Design Pattern-ul Singleton
Cred ca este unul dintre cele mai cunoscute design pattern-uri. Intentia lui este sa puna la dispozitie la nivel global o instanta unica a unei anumite clase, care, in plus, poate fi initializata lenes (lazy), just-in-time.
Gestionarea crearii instantei cade in sarcina clasei respective. Clasa contine un camp static privat care stocheaza instanta. Accesul din exterior la instanta este obtinut printr-un apel la o metoda sau o proprietate care returneaza valoarea campului static privat. Initializarea instantei se realizeaza la primul acces din exterior.
Zice-se ca trebuie indeplinite urmatoarele trei conditii pentru a renta folosirea unui singleton:
- nu se poate stabili clar carei clase ar trebui sa apartina acea singura instanta
- initializarea lenesa este de dorit
- accesarea de la nivel global nu este prevazuta altfel
"Baaaa! iar a picat curentu'!!"
"Presedintele-i de vina!!! Jos-Ba-ses-cu! Jos-Ba-ses-cu!"
Deci e singleton.
Pasi pentru implementare:
- defineste campul static privat care sa aiba acelasi tip cu al clasei
- defineste metoda sau proprietatea de acces, statica si publica
- pune codul de initializare lenesa in metoda/proprietatea de acces
- seteaza orice constructor sa fie protected sau private
- clientii pot folosi doar metoda/proprietatea de acces pentru a manipula Singleton-ul, si probabil ca ar fi indicat sa nu tina referinte catre el, pentru ca Singleton-ul nu promite ca va da tot timpul aceeasi referinta, ci ca va da tot timpul o singura referinta - daca obiectul se schimba, atunci unii clienti ar putea ramane cu instante mai vechi ale clasei, si mai pot aparea si scurgeri de memorie (memory leak)
Reguli:
- poate fi folosit impreuna cu alte design pattern-uri, gen Abstract Factory, Builder, Prototype, Facade (OK, Façade)
- scopul acestui design pattern este sa poti controla exact numarul de instante care sunt create - nu esti constrans la o singura instanta, poti crea si mai multe
- uneori este folosit (gresit) in locul variabilelor globale - scopul lui nu este sa ofere un punct global de acces pentru o anumita entitate, ci sa controleze creerea acelei entitati, mai clar - a numarului de instante; aparent folosirea prea des a Singleton-ului poate sa insemne ca nu te gandesti suficient la gradul de vizibilitate al obiectului respectiv
Unii oameni (si eu printre ei) intreaba de ce nu s-ar folosi o clasa statica in locul unui Singleton. Conceptual, sunt diferite:
- Singleton-ul promite ca va da acces la o singura instanta, si nu e treaba altora cum e creata acea instanta
- clasa statica e o colectie de metode/campuri/proprietati statice
O suita de implementari care mai de care mai smechere gasiti la Jon Skeet pe site.
O pun si eu pe prima, care e mai proasta pentru ca nu e thread-safe:
public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if (instance==null) { instance = new Singleton(); } return instance; } } int m_counter = 0; public int Increment() { return ++m_counter; } } class MainApp { static void Main() { Console.WriteLine(Singleton.Instance.Increment().ToString()); Console.WriteLine(Singleton.Instance.Increment().ToString()); Console.WriteLine(Singleton.Instance.Increment().ToString()); } }
Design Pattern-ul Prototype
Specifica proprietatile, caracteristicile unui obiect care trebuie creat pornind de la o instanta-prototip speciala. Se evita folosirea operatorului new.Ideea descrisa la sourcemaking.com zice sa faci o clasa abstracta care specifica o metoda virtuala Clone si pastreaza un dictionar de clase "clonabile". Orice clasa care vrea sa intre in jocul asta mosteneste clasa abstracta, se inregistreaza in dictionarul de clase "clonabile" si implementeaza metoda Clone. Clientul nu mai foloseste operatorul new pentru a instantia clase noi, ci apeleaza metoda Clone din clasa de baza, furnizand tipul clasei pe care o doreste creata.
Probabil ca metoda asta merge OK in Java sau C++, dar in C#, unde o clasa nu are voie sa mosteneasca decat o singura alta clasa (si sa implementeze un numar oarecare de interfete), ideea asta ar fi mai putin valida.
Probabil ca metoda asta merge OK in Java sau C++, dar in C#, unde o clasa nu are voie sa mosteneasca decat o singura alta clasa (si sa implementeze un numar oarecare de interfete), ideea asta ar fi mai putin valida.
Pasi pentru implementare:
- adauga metoda Clone in ierarhie
- creeaza dictionarul de clase "clonabile", in care se stocheaza instantele prototip ale diferitelor clase
- creeaza metoda fabrica (factory) care gaseste instanta corecta de prototip si apeleaza Clone pe ea, returnand rezultatul
- clientul inlocuieste apelurile catre new cu apeluri catre metoda fabrica
Reguli:
- uneori poate concura cu alte design pattern-uri creationale, gen Abstract Factory, alteori se poate completa cu ele, de exemplu un Abstract Factory poate stoca un set de Prototipuri din care sa returneze instante
- Prototipurile sunt utile cand initializarea obiectelor este costisitoare, consumatoare de resurse, si exista posibilitati limitate de a varia parametrii de initializare
- pastreaza cate o instanta din fiecare clasa interesanta, folosita pentru crearea de noi instante din clasa respectiva
Exemplu:
abstract class Prototype { public abstract Prototype Clone(); protected string m_info; public Prototype(string info) { m_info = info; } public string Info { get { return m_info; } } } class ObjectKind1 : Prototype { public ObjectKind1(string info) : base(info) { } public override Prototype Clone() { return (Prototype)(new ObjectKind1(m_info)); } } class ObjectKind2 : Prototype { public ObjectKind2(string info) : base(info) { } public override Prototype Clone() { return (Prototype) (new ObjectKind2(m_info)); } } class PrototypeRegistry { private Dictionary<Type, Prototype> m_prototypes = new Dictionary<Type, Prototype>(); public void AddPrototype(Prototype i_object) { m_prototypes[i_object.GetType()] = i_object; } public Prototype GetPrototype(Type i_type) { if (m_prototypes.ContainsKey(i_type)) { return m_prototypes[i_type].Clone(); } return null; } } class MainApp { static void Main() { PrototypeRegistry pr = new PrototypeRegistry(); pr.AddPrototype(new ObjectKind1("I")); pr.AddPrototype(new ObjectKind2("II")); ObjectKind1 c1 = (ObjectKind1)pr.GetPrototype(typeof(ObjectKind1)); Console.WriteLine ("Clonat: {0}", c1.Info); ObjectKind2 c2 = (ObjectKind2)pr.GetPrototype(typeof(ObjectKind2)); Console.WriteLine ("Clonat: {0}", c2.Info); } }
Design Pattern-ul Object Pool (rezervor de obiecte)
Este un design pattern foarte util in situatiile in care initializarea obiectelor este costisitoare si sunt instantiate relativ putine obiecte, dar cu frecventa mare. Poate fi vazut ca un rezervor (pool) de obiecte, pe care poate sa le creeze pe masura ce sunt necesare, in general pana ajunge la o anumita limita - de exemplu, vor fi create cel mult 20 de conexiuni cu o baza de date.
Obiectele pot fi cerute de clienti, folosite, apoi returnate in rezervor cand nu mai este nevoie de ele, putand fi apoi cerute de catre alti clienti. Daca este necesar se poate elimina limita asupra numarului de obiecte, in acest caz fiind necesar sa se curete periodic obiectele ramase nefolosite.
De multe ori rezervorul de obiecte este un Singleton, in felul acesta toate obiectele nefolosite pot fi gestionate de aceeasi entitate.
Obiectele trebuie returnate in rezervor cand nu mai sunt necesare, altfel pot aparea scurgeri de resurse. Desigur, este necesar ca obiectele, odata returnate in pool, sa poata fi refolosite si cand sunt cerute din nou.
Obiectele pot fi cerute de clienti, folosite, apoi returnate in rezervor cand nu mai este nevoie de ele, putand fi apoi cerute de catre alti clienti. Daca este necesar se poate elimina limita asupra numarului de obiecte, in acest caz fiind necesar sa se curete periodic obiectele ramase nefolosite.
De multe ori rezervorul de obiecte este un Singleton, in felul acesta toate obiectele nefolosite pot fi gestionate de aceeasi entitate.
Obiectele trebuie returnate in rezervor cand nu mai sunt necesare, altfel pot aparea scurgeri de resurse. Desigur, este necesar ca obiectele, odata returnate in pool, sa poata fi refolosite si cand sunt cerute din nou.
Pasi pentru implementare:
- creeaza clasa ObjectPool, care sa contina lista/array de obiecte Object
- creeaza metode Acquire si Release (sau echivalente) in clasa respectiva, pentru a cere un obiect din rezervor, respectiv pentru a-l inapoia
- ObjectPool trebuie sa fie Singleton
- clientii trebuie sa apeleze Acquire, sa primeasca o instanta de Object, pe care sa o foloseasca, iar la sfarsit sa apeleze Release pe ObjectPool
class MainApp { public const int MAX_RESOURCES = 3; // cel mult 3 resurse public const int MAX_CLIENTS = 7; // pentru 7 clienti public static Random Random = new Random(); static void Main() { for (int i = 0; i < MAX_CLIENTS; i++) { new Client(i).Execute(); } } } /* Resursa primeste un string la initializare si stie sa execute DoStuff, comanda a carei executie dureaza un interval oarecare de timp */ class Resource { string m_info; public Resource(string i_info) { m_info = i_info; } public void DoStuff(int i_client) { Console.WriteLine(String.Format("Am executat resursa {0} pentru clientul {1}" , m_info, i_client.ToString())); Thread.Sleep(Fa.Random.Next(3000)); } } /* Pool-ul imparte resurse, dar numai cat timp le are disponibile; incepe de la 0 si le creeaza pe masura ce sunt necesare. Este implementat ca Singleton. */ class ObjectPool { int m_counter = 0; List<resourcewrapper> m_pool = new List<resourcewrapper>(); object toLock = new object(); #region Singleton stuff ObjectPool() { } static public ObjectPool Instance { get { return m_instance; } } static ObjectPool m_instance = new ObjectPool(); #endregion Singleton stuff public Resource Acquire() { lock (toLock) { ResourceWrapper first = m_pool.FirstOrDefault(e => !e.Taken); if (null != first) { first.Taken = true; return first.Resource; } if (Fa.MAX_RESOURCES > m_pool.Count()) { Resource res = new Resource("Numarul " + (++m_counter).ToString()); ResourceWrapper rw = new ResourceWrapper() { Resource = res, Taken = true }; m_pool.Add(rw); return res; } } return null; } public void Release(Resource i_res) { lock(toLock) { var first = m_pool.First(e => e.Resource == i_res); if (null != first) first.Taken = false; } } class ResourceWrapper { public Resource Resource { get; set; } public bool Taken { get; set; } } } /* Clientul incearca sa obtina resursa, pana cand reuseste; intre incercarile nereusite asteapta un interval aleator de timp. */ class Client { int m_id; public Client(int i_id) { m_id = ++i_id; } public void Execute() { new Thread(new ThreadStart(Do)).Start(); } void Do() { int counter = 0; Resource got = null; // acquire do { got = ObjectPool.Instance.Acquire(); if (null == got) Console.WriteLine(String.Format("Client {0} - acquire #{1} esuat" , m_id.ToString(), (++counter).ToString())); Thread.Sleep(Fa.Random.Next(1000)); } while (null == got); // use Console.WriteLine(String.Format("Client {0} - acquire #{1} REUSIT" , m_id.ToString(), (++counter).ToString())); got.DoStuff(m_id); // release Console.WriteLine(String.Format("Client {0} - release" , m_id.ToString())); ObjectPool.Instance.Release(got); } }
Niciun comentariu:
Trimiteți un comentariu
S-ar putea sa nu vedeti comentariul aparand imediat, asta inseamna ca el asteapta aprobarea mea. Aceasta e o masura anti-SPAM.