Design Patterns

Singleton

Pattern-ul singleton face parte din categoria pattern-urilor creationale deoarece este responsabil de crearea unui obiect. Singleton este responsabil de crearea unei instante unice a unei clase si asigura un punct comun de acces la acea instanta. Prezint mai jos o varianta de implementare a unui singleton:

class Singleton
{
    private static Singleton _instance;

    private Singleton() { }

    public static Instance
    {
        if (_instance == null)
            _instance = new Singleton();
        return _instance;
    }
}

Prin declararea constructorului privat impiedicam instantierea directa a clasei Singleton, astfel incat singurul mod de a obtine o instanta Singleton este prin intermediul proprietatii statice Instance, care furnizeaza aceeasi instanta unica la fiecare apel. Urmatoarea metoda intoarce intotdeauna valoarea true:

bool TestInstancesEquality()
{
    return Singleton.Instance == Singleton.Instance;
}


Diagrama UML

O varianta simplificata de singleton poate fi implementata cu ajutorul unei constante runtime (readonly) dupa cum urmeaza:

class Singleton
{
    private Singleton() { }

    public static readonly Singleton Instance = new Singleton();
}

Niciuna din variantele prezentate nu este thread-safe. Urmatoarea secventa ilustreaza o varianta de singleton care este thread-safe. In implementare am folosit pattern-ul double-checked-locking si un obiect de sincronizare.

class Singleton
{
    private static Singleton _instance;
    private static object _syncObject = new object();

    private Singleton() { }

    public static Instance
    {
        if (_instance == null)
        {
            lock (_syncObject)
            {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

Se poate intampla ca in acelasi timp, doua threaduri sa testeze campul _instance daca este null, ambele threaduri gasindu-l null, fapt care face ca ambele threaduri sa continue executia in interiorul primului bloc if. Ambele threaduri ajung cu executia pana la sectiunea lock, moment in care doar unul din threaduri are acces in interiorul sectiunii lock, celalalt thread asteptand pana primul thread paraseste sectiunealock. In momentul in care thread-ul paraseste sectiunea lock, celalalt thread in asteptare va continua executia intrand in sectiunea lock si testand pentru a doua oara campul _instance, care a fost in prealabil setat de primul thread care a intrat in sectiunea lock. Astfel, campul nefiind null, threadul isi va continua executia iesind din sectiune si returnand aceeasi valoare a instantei _instance ca si primul thread.

Nota: Abuzul de obiecte singleton conduce spre un design prost, facand din singleton un anti-pattern. Se recomanda sa nu se abuzeze de obiecte singleton si sa se foloseasca doar daca sunt absolut necesare si sa nu se recurga la ele ca la o modalitate de acces la un serviciu din orice punct al unei aplicatii. Abuzul apeluri ale unui singleton poate fi chiar contraperformant datorita testari campului _instance din interiorul obiectului.


Prototype (Prototipul)

Pattern-ul prototype face parte din categoria pattern-urilor creationale si pune la dispozitie un obiect prototip care este responsabil de crearea de noi obiecte de acelasi tip cu prototipul (clone).

Acest pattern este folosit in cazul in care procesul de constructie a unui obiect este complex si se doreste o metoda rapida de creare de noi obiecte prin generarea unor copii (clone) a obiectului tinta.


Diagrama UML

Prototype este implementat ca o interfata sau clasa abstracta, care furnizeaza o metoda abstracta generatoare a unui nou obiect de acelasi tip cu clasa prototype. In figura de mai sus se observa clasa abstracta Prototype care contine o metoda Clone. Clasele concrete care mostenesc clasa Prototype, implementeaza metoda abstracta Clone.

Mai jos, prezint un exemplu de pattern prototype:

abstract class Prototype
{
    protected Prototype(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }

    public abstract Prototype Clone();
}

class ConcretePrototypeA : Prototype
    {
        public ConcretePrototypeA(string name) : base(name) { }

        public override Prototype Clone() { return (Prototype)MemberwiseClone(); }
}

class ConcretePrototypeB : Prototype
{
    public ConcretePrototypeB(string name) : base(name) { }

    public override Prototype Clone() { return (Prototype)MemberwiseClone(); }
}

Exemplu de utilizare:

Prototype p1 = new ConcretePrototypeA("P1");
Prototype c1 = p1.Clone();

Prototype p2 = new ConcretePrototypeB("P2");
Prototype c2 = p2.Clone();

Factory Method (Constructorul virtual)

Factory method este un pattern creational care defineste o interfata folosita la crearea unor obiecte, cu mentiunea ca subclasele implementatoare ale interfetei poarta raspunderea instantierii obiectelor. Deci, interfata factory method deleaga sarcina crearii obiectelor clasei care o implementeaza.


Diagrama UML

Acest pattern este foarte folosit atunci cand se pune accent pe programarea cu interfete in detrimentul implementarilor sau cand nu se cunoaste in prealabil tipul obiectului tinta, care va fi generat ulterior pe baza unui algoritm specificat.

Prezint mai jos un exemplu:

abstract class Product { }

class ConcreteProductA : Product { }

class ConcreteProductB : Product { }

interface ICreator
{
    Product FactoryMethod();
}

class ConcreteCreatorA : ICreator
{
    public Product FactoryMethod()
    {
        return new ConcreteProductA();
    }
}

class ConcreteCreatorB : ICreator
{
    public Product FactoryMethod()
    {
        return new ConcreteProductB();
    }
}

Exemplu de utilizare:

var creators = new ICreator[]{ new ConcreteCreatorA(), new ConcreteCreatorB()};
foreach (var creator in creators)
    Console.WriteLine("Created {0}", creator.FactoryMethod().GetType().Name);

Adapter (Adaptorul)

Adapter este un pattern structural, care face ca clase incompatibile sa poate lucra impreuna prin adaptarea uneia dintre ele la cerintele celeilalte. Acest lucru se face "convertind" interfata unei clase la o interfata client.

Sa vedem acum cum putem realiza acest lucru. Se recurge la conceptele de realizare si delegare. Adica, se defineste o interfata client (acea structura pe care ulterior o va folosi un client) care va fi realizata (implementata sau suprascrisa) de asa zisul adaptor (clasa adapter). Adapter-ul, prin delegare, va apela la serviciile unei structuri incompatibile cu asteptarile clientului (clasa adaptata, adaptee).

Pattern-ul este utilizat atunci cand este necesara folosirea unei clase deja existente dar a carei interfata nu corespunde cu cea dorita de un client. Alt caz de utilizare consta in crearea unei clase reutilizabile care sa poata conlucra cu clase incompatibile din punct de vedere al clientului (utilizatorul adaptorului).

Mai jos avem un exemplu:

class Target
{
    public virtual void Request()
    {
        Console.WriteLine("Target.Request()");
    }
}

class Adapter : Target
{
    private readonly Adaptee _adaptee = new Adaptee();

    public override void Request()
    {
        _adaptee.SpecificRequest();
    }
}

class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Adaptee.SpecificRequest()");
    }
}

Exemplu de utilizare:

Target target = new Adapter();
target.Request();

Facade (Fatada)

Facade e un alt pattern structural care defineste o interfata unificata a unor interfete ale unui subsistem sau subsisteme. Aceasta interfata de nivel inalt are scopul de a usura utilizarea serviciilor oferite de subsistemul sau subsistemele aferente.

Pattern-ul este utilizat ori de cate ori se doreste simplificarea utilizarii unor subsisteme complexe sau cand se doreste o stratificare arhitecturala (layers).

Urmariti urmatorul exemplu:

class Subsystem1
{
    public void Operation() { Console.WriteLine("Subsystem1.Operation()"); }
}

class Subsystem2
{
    public void Operation() { Console.WriteLine("Subsystem2.Operation()"); }
}

class Subsystem3
{
    public void Operation() { Console.WriteLine("Subsystem3.Operation()"); }
}

class Facade
{
    private Subsystem1 _sys1 = new Subsystem1();
    private Subsystem2 _sys2 = new Subsystem2();
    private Subsystem3 _sys3 = new Subsystem3();

    public void Method1()
    {
        Console.WriteLine("{0}Facade.Method1()", Environment.NewLine);
        _sys1.Operation();
        _sys2.Operation();
        _sys3.Operation();
    }

    public void Method2()
    {
        Console.WriteLine("{0}Facade.Method2()", Environment.NewLine);
        _sys2.Operation();
    }
}

Exemplu de utilizare:

var facade = new Facade();
facade.Method1();
facade.Method2();

Observer

Observer este un pattern comportamental care defineste o abstractiune prin care daca un obiect isi modifica starea, toate obiectele dependente de acesta vor fi notificate. Unui subiect i se pot asocia unul sau mai multi observeri, care sunt notificati de subiect ori de cate ori acesta isi modifica starea.

Acest pattern este foarte utilizat si se aplica cand se doreste ca modificari intr-un obiect sa se oglindeasca in alte obiecte care nu au o legatura stransa cu acesta.

Un exemplu de utilizare il consta sistemul de logare a unei aplicatii. Logica de logare a unei aplicatii nu trebuie sa faca parte din business logic, ci trebuie sa fie izolata de aceasta. Drept urmare, logarea aplicatiei se imnplementeaza ca un observer care monitorizeaza evenimente sau schimbari de stare din business logic. Logarea trebuie sa fie un sistem de sine statator, care sa nu blocheze apelurile metodelor din business logica aplicatiei. Business logica aplicatiei notifica sistemul de logare iar acesta din urma receptioneaza semnalele si le proceseaza de regula pe un fir de executie aditional, pentru a nu bloca executia programului.

Mai jos, prezint urmatorul exemplu:

public abstract class Subject
{
    private readonly IList<Observer> _observers = new List<Observer>();

    public void Register(Observer observer) { _observers.Add(observer); }
    public void Unregister(Observer observer) { _observers.Remove(observer); }
    public void InvokeChange()
    {
        foreach (var o in _observers)
            o.Update();
    }
}

public abstract class Observer
{
    public abstract void Update();
}

public class Domain : Subject
{
    private string _state;
    public string State
    {
        get { return _state; }
        set
        {
            _state = value;
            InvokeChange();
        }
    }
}

public class Logger : Observer
{
    private string _state;
    private readonly string _name;
    private readonly Domain _subject;

    public Logger(Domain subject, string name)
    {
        _subject = subject;
        _name = name;
    }

    public override void Update()
    {
        _state = _subject.State;
        Console.WriteLine("{0}'s state changed to {1}", _name, _state);
    }
}

Exemplu de utilizare:

var subject = new Domain();

subject.Register(new Logger(subject, "FileLogger"));
subject.Register(new Logger(subject, "EventLogLogger"));

subject.State = "running";

Niciun comentariu:

Trimiteți un comentariu