niedziela, 29 lipca 2012

Wzorce projektowe

Spis postów na temat wzorców projektowych:

SOLID:



Wzorce:


[Wzorce projektowe] State

Analogia z życia:

Chcąc pobrać pieniądze idziemy do bankomatu. Po włożeniu karty i podaniu PINu możemy zgłosić żądanie pobrania pieniędzy. Jeśli spełnione jest kilka warunków, to maszyna wyda nam kwotę jakiej żądamy. Może się jednak zdarzyć tak, że nie mamy środków na koncie, albo nasze konto zostało zablokowane. Inną możliwością jest brak funduszy w maszynie. Każda z tych sytuacji jest osobnym stanem, który musi być rozpoznany i obsługiwany przez bankomat.





Zastosowanie:

W każdej sytuacji, gdy mamy do czynienia z obiektem, którego akcje zależą od stanu w jakim się znajduje. Stan obiektu jest zmieniany w czasie gdy aplikacja jest uruchomiona. Przejścia pomiędzy stanami niekonieczną muszą być dozwolone w pewnym kontekście, co też należy obsłużyć.

Zasada działania:

Głównym rdzeniem programu jest obiekt zmieniający stany dynamicznie. Stan reprezentuje się np. za pomocą enuma. Obiekt udostępnia metodę do zmiany stanu i sprawdzenia, czy dany stan można w danej chwili osiągnąć. Każdy stan implementuje jeden wspólny interfejs i udostępnia metody reprezentujące akcje.

Przykład implementacyjny:

    public interface ITvActions
    {
        void TurnOn();
        void TurnOff();
        void ChangeChannel(int n);
    }
public abstract class TVState
    {
        public Tv Owner { get; set; }

        protected TVState(Tv @object)
        {
            Owner = @object;
        }
    }
 public class RunningState : TVState, ITvActions
    {
        public RunningState(Tv @object) : base(@object)
        {
        }

        public void TurnOn()
        {
            Console.WriteLine("TV already running");
        }

        public void TurnOff()
        {
            Owner.IsRunning = false;
            Owner.SetState("Off");
            Console.WriteLine("Turned Off");
        }

        public void ChangeChannel(int n)
        {
            Owner.CurrentChannel = n;
            Console.WriteLine("Switched to " + n);
        }
    }
class TurnedOffState : TVState, ITvActions
    {
        public TurnedOffState(Tv @object) : base(@object)
        {
        }

        public void TurnOn()
        {
            Owner.SetState("Running");
            Owner.IsRunning = true;
            Console.WriteLine("Succesfully turned on");
        }

        public void TurnOff()
        {
            Console.WriteLine("Already turned off");
        }

        public void ChangeChannel(int n)
        {
            Console.WriteLine("Unable to change");
        }
    }
public class Tv : ITvActions
    {
        public ITvActions State { get; set; }
        public int CurrentChannel { get; set; }
        public bool IsRunning { get; set; }

        public Tv()
        {
            IsRunning = true;
            CurrentChannel = 1;
            State = new RunningState(this);
        }

        public void TurnOn()
        {
            State.TurnOn();
        }

        public void TurnOff()
        {
            State.TurnOff();
        }

        public void ChangeChannel(int n)
        {
            State.ChangeChannel(n);
        }

        public void SetState(string state)
        {
            switch (state)
            {
                case "Running":
                    State = new RunningState(this);
                    break;
                case "Off":
                    State = new TurnedOffState(this);
                    break;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Tv tv = new Tv();
            tv.TurnOff();
            tv.ChangeChannel(4);
            tv.TurnOn();
            tv.ChangeChannel(1);
            Console.ReadKey();
        }
    }

sobota, 28 lipca 2012

[Wzorce projektowe] Service Locator

Analogia z życia:

Dzisiejsze telefony komórkowe oferuję wiele funkcjonalności. Możemy na przykład napisać wiadomość tekstową lub do kogoś zadzwonić. Z punktu widzenia użytkownika, wysłanie takiej wiadomości jest wykorzystaniem pewnej usługi. Piszemy wiadomość, podajemy numer i klikamy wyślij. To w jaki sposób wiadomość zostanie przekazana i ile za nią zapłacimy zależy między innymi od operatora sieci. Wkładając kartę SIM, możemy na starcie określić wspomniane warunki, aby później po uruchomieniu telefonu w każdej chwili korzystać z usługi w wybranej wersji.


Zastosowanie:

Wzorzec lokatora usług znajduje zastosowanie w sytuacji, gdy chcemy odseparować obiekty, od usług z których korzystają. Zwiększy to niezależność komponentów i ułatwi przyszłe wprowadzanie modyfikacji. Dodatkowo mając kilka podobnych serwisów, można wybierać któryś z nich przed uruchomieniem aplikacji przez odpowiednie ustawienia konfiguracyjne. Rozwiązanie takie wspiera testowalność.

Zasada działania:

Wprowadza się klasę lokatora usług, zawierającą statyczne pole typu HashTable z usługami. Kluczem do usługi w tablicy może być typ usługi lub podana nazwa. Dodatkowo klasa lokatora usług zawiera statyczne metody do pobrania i dodania nowego serwisu. Serwisy można dodawać na starcie aplikacji, np. w WPF czy Silverlight w metodzie OnStartup.

Przykład implementacyjny:

 public interface ILogger
    {
        void Write(string msg);
    }

    public class ConsoleLogger : ILogger
    {
        public void Write(string msg)
        {
            Console.WriteLine(msg);
        }
    }
internal class ServiceLocator
    {
        private static readonly Hashtable Services 
            = new Hashtable();

        public static void AddService<T>(T service)
        {
            Services.Add(typeof(T),service);
        }

        public static void AddService<T>(string name, T service)
        {
            Services.Add(name, service);
        }

        public static T GetService<T>()
        {
            return (T)Services[typeof(T)];
        }

        public static T GetService<T>(string name)
        {
            return (T)Services[name];
        }

    }
public class Fibonnaci
    {
        public int Number { get; set; }
        public int LastNum { get; set; }

        public Fibonnaci()
        {
            LastNum = 0;
            Number = 1;
        }

        public int Next()
        {
            var n = LastNum + Number;
            LastNum = Number;
            Number = n;
            IsDivisibleBy4();
            return n;
        }

        public void IsDivisibleBy4()
        {
            if (Number%4 != 0) return;
            var logger = ServiceLocator.GetService<ILogger>();
            logger.Write(String.Format
                             ("Number {0}, last : {1}",Number, LastNum));
        }
    }
class Program
    {
        static Program()
        {
            ServiceLocator.AddService<ILogger>(new ConsoleLogger());
        }

        static void Main(string[] args)
        {
            Fibonnaci fibonnaci = new Fibonnaci();
            for (int i = 0; i < 100; i++)
            {
                fibonnaci.Next();
            }
            Console.ReadKey();
        }
    }

[Wzorce projektowe] Repository

Analogia z życia:

Chcąc wypożyczyć książki udajemy się do biblioteki. Osoby pracujące tam pomagają nam znaleźć interesujące nas pozycje, podają z magazynu egzemplarze, które nas interesują oraz wstawiają te, które chcemy oddać. W przypadku bibliotek samoobsługowych musielibyśmy wiedzieć o tym, w jaki sposób wyszukiwać i gdzie fizycznie szukać konkretnych egzemplarzy.



Zastosowanie:

Wszędzie tam, gdzie mamy do czynienia z dostępem do danych : połączenia SQL, pobieranie danych z plików, czy webserwisów. Wzorzec repozytorium zapewnia enkapsulację kodu odpowiadającego za połączenie ze źródłem danych w taki sposób. Jest to zatem warstwa pośrednia pomiędzy buissness logic, a źródłem danych, co ułatwia np. testowanie. Wzorzec często występuję w parze z wzorcem Unit of Work odpowiadającym za zatwierdzanie lub wycofywanie zmian.

Zasada działania:

Wprowadza się obiekt repozytorium udostępniający pewne API. W skład API wchodzą takie metody, jak dodawanie encji, usuwanie czy przeszukiwanie kolekcji encji. Bazowe API może być generycznym interfejsem, z którego dziedziczą konkretne repozytoria.

Przykład implementacyjny:

public interface IEntity
    {
        int Id { get; set; }
    }

    public class Customer : IEntity 
    {
        public string Name { get; set; }
        public string Mail { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public int Id { get; set; }
    }
public interface IRepository<T>
        where T : IEntity
    {
        void Add(T instance);
        void Remove(T instance);
        IEnumerable<T> Find(Predicate<T> predicate);
    }

    public class CustomerRepository : IRepository<Customer>
    {
        private List<Customer> _entities;

        private int _count;

        public CustomerRepository()
        {
            try
            {
               LoadDataFromXml();
            }
            catch (Exception)
            {
               Console.WriteLine("Unable to load data");
            }
        }

        public void LoadDataFromXml()
        {
            _entities = new List<Customer>();
            XDocument doc = XDocument.Load("Records.xml");
            var list = doc.Elements("records").Elements("record");
            foreach (var element in list)
            {
                IEnumerable<XElement> properties = element.Elements();
                var customer = new Customer();
                customer.Id = _count++;
                customer.Name = properties.ElementAt(0).Value;
                customer.Mail = properties.ElementAt(1).Value;
                customer.Address = properties.ElementAt(2).Value;
                customer.City = properties.ElementAt(3).Value;
                _entities.Add(customer);
                }
        }

        public void Add(Customer instance)
        {
            instance.Id = _count++;
            _entities.Add(instance);
        }

        public void Remove(Customer instance)
        {
            _entities.Remove(instance);
        }

        public IEnumerable<Customer> Find(Predicate<Customer> predicate)
        {
            return _entities.Where(customer => predicate(customer));
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            IRepository<Customer> repository = new CustomerRepository();

            var results = repository.Find(c => c.City.StartsWith("C"));
            foreach (Customer customer in results)
            {
                Console.WriteLine(customer.City);
            }

            repository.Add(new Customer()
                               {
                                   Name = "Jonnie Walker",
                                   Address = "Elm Street 12/9",
                                   Mail = "jwalker@mit.edu",
                                   City = "NY"
                               });

            Console.ReadKey();
        }
    }

wtorek, 24 lipca 2012

[Wzorce projektowe] Prototype

Analogia z życia:

Podróżując lubimy robić zdjęcia. Są one pamiątką z miejsc, które odwiedziliśmy i chwil, które przeżyliśmy. Może się tak zdarzyć, że wywołamy pewne zdjęcie, na którym jest grupa osób. Gdy każda z tych osób obejrzy zdjęcie, dojdzie do wniosku, że chce takie samo. Możemy oczywiście zebrać całą grupę, pojechać w to samo miejsce i zrobić jeszcze zdjęcie kilka razy. Ale na 100% będzie ono inne, a jego zrobienie będzie nas kosztowało sporo wysiłku, czasu a może i pieniędzy. Można także zrobić kopię u fotografa, przez co zaoszczędzimy sobie wymienionych kłopotów.

Zastosowanie:

Prototyp jest jednym z najprostszych, o ile nie najprostszym wzorcem projektowym. Stosuje się go wszędzie tam, gdzie jest potrzebna kopia pewnego obiektu, a jej utworzenie w sposób tradycyjny wydaje się bezsensowne. Proces tworzenia może być np. bardzo długi (pobieranie z bazy danych, pobieranie pliku), W innym przypadku odtworzenie obiektu może wymagać wywołania szeregu metod lub też stan obiektu może być w pewnym sensie niestabilny i trudny do odtworzenie w ogóle.


Zasada działania:


Używając języka C# wystarczy zaimplementować interfejs ICloneable dla obiektu, który chcemy klonować, pamiętając o zasadach tworzenia kopii : Deep vs Shallow Copy. Dla obiektów z propercjami prostych typów wystarczy metoda MemberwiseClone().

Przykład implementacyjny:


public class RandomMelody : ICloneable
    {
        public List<int> Frequencies { get; set; }
        public List<int> Durations { get; set; }

        internal RandomMelody(){}

        public RandomMelody(int n)
        {
            Frequencies = new List<int>();
            Durations = new List<int>();

            Random r = new Random();

            for (int i = 0; i < n; i++)
            {
                Frequencies.Add((r.Next()%300) + 400);
                Durations.Add((r.Next() % 25) + 100);
            }
        }

        public void Play()
        {
            for (int i = 0; i < Frequencies.Count; i++)
            {
                Console.Beep(Frequencies[i], Durations[i]);
            }
        }

        public object Clone()
        {
            var rm = new RandomMelody();
            rm.Durations = new List<int>();
            rm.Frequencies = new List<int>();
            for (int i = 0; i < Durations.Count; i++)
            {
                rm.Durations.Add(Durations[i]);
                rm.Frequencies.Add(Frequencies[i]);
            }
            return rm;
        }
 class Program
    {
        static void Main(string[] args)
        {
            var s1 = new RandomMelody(10);
            var s2 = new RandomMelody(10);
            var s3 = s1.Clone() as RandomMelody;

            Console.WriteLine("Original song");
            s1.Play();
            Console.WriteLine("New song");
            s2.Play();
            Console.WriteLine("Original song");
            s1.Play();
            Console.WriteLine("Copy");
            s3.Play();
        }
    }

poniedziałek, 23 lipca 2012

[Wzorce projektowe] Null Object

Analogia z życia:

Każdy użytkownik komunikatora gadu - gadu ma do dyspozycji kilka statusów. Może być np. dostępny lub niewidoczny. Mając na liście znajomych, nie możemy w prosty sposób rozróżnić kto z nich jest niewidoczny, a kogo nie ma w danej chwili przy komputerze. Komunikator zapewnia jednak, że możemy wysłać wiadomość do każdego z nich : jeśli użytkownik jest przy komputerze, to może nam błyskawicznie odpisać, natomiast gdy go nie ma, wiadomość nie zaginie, lecz zostanie mu dostarczona po zalogowaniu do komunikatora.



Zastosowanie:

Wzorca Null Object można użyć, gdy mamy do czynienia z dużą ilością obiektów, które w pewnym etapie programu mają wartość null. Aby uniknąć wyjątku NullReference oraz sprawdzeń, czy dany obiekt jest nullem, można wprowadzić dodatkowy obiekt bez jakiejkolwiek funkcjonalności, np. zawierający puste metody tego samego interfejsu co zwykły obiekt. Kolejnym powodem, dla którego warto zastosować ten wzorzec jest sytuacja, w której klient pobiera obiekty i nie chcemy, aby zajmował się sprawdzaniem, czy ich wartość jest równa null.

Zasada działania:

Obiekt klienta przechowuje referencję do obiektu typu pewnej klasy abstrakcyjnej. Z tej klasy dziedziczą dwa typy : rzeczywisty obiekt i obiekt - null. Można także wprowadzić kilka obiektów null jako reprezentację różnych stanów "nieaktywności". W niektórych przypadkach warto rozważyć zastosowanie singletonu, jako obiektu - null.

Przykład implementacyjny:

public class Fire
    {
        public int Power { get; set; }
    }

    public abstract class FirefighterBase
    {
        public abstract void Distinguish(Fire fire);

        static InactiveFirefighter _inactive = new InactiveFirefighter();

        public static FirefighterBase NULL
        {
            get { return _inactive; }
        }

        public class InactiveFirefighter : FirefighterBase
        {
            public override void Distinguish(Fire fire)
            {
                Console.WriteLine("Cannot help you");
            }
        }
    }

    public class ActiveFirefighter : FirefighterBase
    {
        private int _strength;

        public ActiveFirefighter()
        {
            Random r = new Random();
            _strength = r.Next() % 25;
        }

        public override void Distinguish(Fire fire)
        {
            fire.Power -= _strength;
            Console.WriteLine("Distinguishing...");
        }
    }
 public static class FireBrigade
    {
        public static FirefighterBase GetFireFighter(int id)
        {
            if(id % 2 ==0)
            {
                return FirefighterBase.NULL;
            }
            else
            {
                return new ActiveFirefighter();
            }
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            Fire f = new Fire() {Power = 300};
            List<FirefighterBase> fighters = new List<FirefighterBase>();
            for (int i = 0; i < 15; i++)
            {
                fighters.Add(FireBrigade.GetFireFighter(i));
            }
            foreach (FirefighterBase firefighterBase in fighters)
            {
                firefighterBase.Distinguish(f);
            }

            Console.WriteLine(String.Format("Fire power is now... {0}",f.Power));

            Console.ReadKey();
        }
    }

wtorek, 17 lipca 2012

[Wzorce projektowe] Memento

Analogia z życia:

Popularny odtwarzacz plików mp3, Winamp oferuje szereg funkcjonalności. Jedną z nich jest losowe wybieranie kolejnych odtwarzanych utworów. Po odsłuchaniu kilku wybranych w ten sposób piosenek, możemy wrócić do pierwszego utwory przechodząc po drodze wszystkie, które do tej pory słuchaliśmy. Następnie można również przejść przez kolejne słuchane utwory aż do ostatniego tak, by potem wylosować kolejny.



Zastosowanie:

Wzorzec Memento jest przydatny w momencie, gdy chcemy w naszej aplikacji zaimplementować funkcjonalność przechodzenia pomiędzy stanami pewnego obiektu, np. cofanie ostatnich zmian. Zatem należy go zastosować w przypadku, gdy potrzebujemy w jakiś sposób śledzić stan naszego obiektu i dokonywać zmian tego stanu.

Zasada działania:

Wprowadza się dwa obiekty : autora (originator), który zmienia swój stan podczas działania programu, oraz dozorcę (caretaker), czyli obiekt który wykonuje operacje na autorze, mające na celu zmianę stanu oraz pobieranie obecnego stanu. Dodatkowo korzysta się z obiektu Memento, który przechowuje stan autora Autor tworzy  mementa, natomiast dozorca utrzymuje je, nie ingerując w ich zawartość. To dozorca żąda obiektów memento od autora i gdy zachodzi taka potrzeba oddaje je autorowi. Same mementa powinny być obiektami przechowującymi jedynie stan - bez zachowania, natomiast stan powinien być interpretowany jedynie przez autora. Przeważnie mementa przechowuje się na stosie, pobierając kolejne stany przy kolejnych żądaniach cofania stanu. Można także wprowadzać operację przywracania stanu, co wiąże się z wprowadzeniem drugiego stosu. W przypadku niektórych obiektów można zamiast stanu przechowywać jedynie wykonane operacje i przy próbie przywracania stanu stosować operacje odwrotne, ale nie w każdym przypadku jest to możliwe.

Przykład implementacyjny:

public interface IMemento
    {
        object State { get; set; }
    }

    public class CalculatorState : IMemento
    {
        private object _state;

        public object State
        {
            get { return _state; }
            set { _state = value; }
        }
    }

    public class SimpleCalculator
    {
        public int Value { get; set; }

        public IMemento GetState()
        {
            return new CalculatorState() {State = Value};
        }

        public void RestoreState(IMemento memento)
        {
            Value = int.Parse(memento.State.ToString());
        }
    }

    public class CalculatorCaretaker
    {
        private readonly Stack<IMemento> _states = new Stack<IMemento>();

        public void StoreState(IMemento item)
        {
            _states.Push(item);
        }

        public void UndoFunctionality(SimpleCalculator calc)
        {
            if(_states.Count > 1)
            {
                _states.Pop();
                calc.RestoreState(_states.Peek());
            }
        }
    }
 class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press number to add, press u to undo");
            CalculatorCaretaker caretaker = new CalculatorCaretaker();
            SimpleCalculator calculator = new SimpleCalculator();

            while(true)
            {
                var n = Console.ReadLine();
                if(n.Contains('u'))
                {
                    caretaker.UndoFunctionality(calculator);
                }
                else
                {
                    int nb;
                    bool b = int.TryParse(n, out nb);
                    if(b)
                    {
                        calculator.Value += nb;
                        caretaker.StoreState(calculator.GetState());
                    }
                    else
                    {
                        break;
                    }
                }
                Console.WriteLine(String.Format("The value is : {0} ", calculator.Value));
            }
        }
    }

sobota, 14 lipca 2012

[Wzorce projektowe] Mediator

Analogia z życia:

Chyba każdy internauta korzystał kiedyś z forów internetowych. Służą one do wymiany informacji na różne tematy między uczestnikami. Każdy użytkownik (nie licząc administracji) ma takie same prawa i także dotyczą go te same obowiązki. Każdy z każdym może się wymieniać informacjami na dowolny temat, a miejscem, gdzie ta wymiana danych się dokonuje jest temat w odpowiednim dziale na forum.




Zastosowanie:

Wzorzec Mediatora okazuje się przydatny, gdy mamy do czynienia z grupą obiektów o podobnych cechach, które mają się ze sobą w pewien sposób komunikować. Wprowadza się zatem dodatkowy, scentralizowany obiekt mediatora, który zbiera informacje od zwykłych obiektów, zwanych kolegami i rozprowadza je zgodnie z pewną logiką, na przykład tylko do uprawnionych obiektów.

Zasada działania:

Wprowadza się pewną klasę bazową z której dziedziczą obiekty zwane kolegami. Każdy obiekt będzie miał w sobie zaszytą logikę przekazywania wiadomości do instancji klasy mediatora. Mediator natomiast zajmuje się rozprowadzeniem wiadomości po wszystkich obiektach - kolegach, którzy są taką wiadomością w jakiś sposób zainteresowani.

Przykład implementacyjny:

public abstract class Warship
    {
        private readonly IWarshipsController _controller;

        protected Warship(bool isActive, IWarshipsController controller)
        {
            _controller = controller;
            IsActive = isActive;
            _controller.RegisterWarship(this);
        }

        public virtual int Range { get; set; }
        public bool IsActive { get; set; }

        private int _lattitude;
        public int Lattitude
        {
            get { return _lattitude; }
            set
            {
                _lattitude = value;
                _controller.ReceiveCoordinatesNotification(this);
            }
        }

        private int _longitude;
        public int Longitude
        {
            get { return _longitude; }
            set
            {
                _longitude = value;
                _controller.ReceiveCoordinatesNotification(this);
            }
        }

        public void InformAboutFellows()
        {
            Console.WriteLine(this.GetType().Name.ToString() + " Acknowledged");
        }
    }

    public class Cruiser : Warship
    {
        public Cruiser(bool i, IWarshipsController c) : base(i,c)
        {
            
        }

        public override int Range
        {
            get
            {
                return 100;
            }
        }
    }

    public class Destroyer : Warship
    {
        public Destroyer(bool i, IWarshipsController c) : base(i,c)
        {
            
        }

        public override int Range
        {
            get
            {
                return 50;
            }
        }
    }
 public interface IWarshipsController
    {
        void ReceiveCoordinatesNotification(Warship sender);
        void RegisterWarship(Warship warship);
    }

    public class MarinaryGovernment : IWarshipsController
    {
        private List<Warship> _ships = new List<Warship>();

        public void ReceiveCoordinatesNotification(Warship sender)
        {
            foreach (Warship warship in _ships.Where(x => x!=sender))
            {
                if(warship.Lattitude - sender.Lattitude < warship.Range
                    || warship.Longitude - sender.Longitude < warship.Range)
                {
                    warship.InformAboutFellows();
                }

            }
        }

        public void RegisterWarship(Warship warship)
        {
            if(!_ships.Contains(warship))
            {
                _ships.Add(warship);
            }
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            MarinaryGovernment gov = new MarinaryGovernment();
            Warship c1 = new Cruiser(true, gov);
            Warship c2 = new Cruiser(true, gov);
            Warship c3 = new Cruiser(true, gov);

            Warship d1 = new Destroyer(true, gov);
            Warship d2 = new Destroyer(true, gov);
            Warship d3 = new Destroyer(true, gov);

            c1.Longitude = 150;
            c2.Longitude = 125;
            c3.Longitude = 50;

            d1.Lattitude = 75;
            d2.Lattitude = 100;
            d3.Lattitude = 130;

            Console.ReadKey(true);

        }
    }

[Wzorce projektowe] Lazy Load

Analogia z życia:

Jednym z codziennie używanych produktów jest cukier. Idąc do sklepu możemy wpaść na pomysł, że zrobimy sobie zapas cukru na cały rok. Zabieramy zatem kilkanaście kilogramów i udajemy się do kasy. Zaletą takiego podejścia jest to, że nie musimy martwić się o to, czy nam zabraknie cukru przez wiele miesięcy, wadami kwota jaką płacimy, kłopoty z przetransportowaniem do domu i brak miejsca do składowania. Może się też zdarzyć, że za tydzień przestaniemy słodzić i wtedy cały zakup okaże się niepotrzebny.



Zastosowanie:

Wzorzec lazy load stosuje się wszędzie tam, gdzie pobranie obiektu wymaga dodatkowego ładowania danych, które nie są używane w momencie, kiedy korzystamy z obiektu. Do tego wzorca należy podchodzić ostrożnie, gdyż np. zbyt wiele zapytań do bazy danych może się okazać niekorzystne pod względem wydajności dla całej aplikacji. Zatem decydując się na zastosowanie tego wzorcu należy brać pod uwagę zarówno ilość zapytań, jak i wielkość danych, jakie zwracają te zapytania. Dzięki odpowiedniej implementacji wzorzec zapewni nam, że dane będą ładowane w momencie, kiedy zajdzie potrzeba ich użycia, a jeśli taka potrzeba nie zajdzie to nie załadowane zostaną wcale (z korzyścią dla wydajności aplikacji).

Zasada działania:

Wzorzec można zaimplementować na cztery sposoby.

Lazy Initialization:

Jest to najprostsze podejście, które wykorzystuje propercje z "backing field". W momencie, gdy odczytujemy wartość propercji, sprawdzane jest czy zainicjowane zostało już odpowiadające jej pole, a jeśli nie, to inicjalizujemy je. Wadą tej metody jest fakt, iż inne operacje klasy nie mogą odczytywać wartości w żaden inny sposób niż przez propercję (pole może nie być zainicjalizowane). W języku C# przy implementacji Lazy Initialization można użyć operatora ??

public class LazyInitializedObject
    {
        private HeavyObject _obj;
        public HeavyObject Obj
        {
            get
            {
                if(_obj == null)
                    _obj = new HeavyObject();
                return _obj;
            }
        }

        private HeavyObject _obj2;
        public HeavyObject Obj2
        {
            get { return _obj2 ?? (_obj2 = new HeavyObject()); }
        }
    }

    public class HeavyObject
    {
        public HeavyObject()
        {
            //symulacja długich operacji odczytu z bazy danych
            Thread.Sleep(300);
        }
    }  

Virtual Proxy:

Ideą tej metody jest zastosowanie klasy, która wygląda dla klienta jak obiekt, który nas interesuje, ale tak na prawdę klasa proxy steruje dostępem do właściwego obiektu. Problemem jest tutaj tożsamość obiektu proxy, który klient może porównywać z innymi rzeczywistymi obiektami. Można ten problem rozwiązać przeładowując metody Equals i GetHashCode. Jeśli chodzi o implementację, to warto zastosować prostą fabrykę. Klient wysyła żądanie do fabryki o nowy obiekt. Fabryka zwraca obiekt proxy, który zawiera w sobie obiekt właściwy. Proxy steruje logiką przy wołaniu usług z właściwego obiektu, a także zapewnia tożsamość obiektu.


public class Student
    {
        public Student()
        {
            //symulacja pobierania danych
            Thread.Sleep(300);
        }
    }

    public class StudentBook
    {
        public int Identity { get; set; }

        public virtual Student Entity { get; set; }

        public override int GetHashCode()
        {
            return Identity.GetHashCode();
        }
    }

    public class StudentBookProxy : StudentBook
    {
        public override Student Entity
        {
            get
            {
                if(base.Entity == null)
                    base.Entity = new Student();
                return base.Entity;
            }
            set
            {
                base.Entity = value;
            }
        }

        public override bool Equals(object obj)
        {
            var st = obj as StudentBook;
            if (st == null) return false;
            return st.Identity == this.Identity;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    public class StudentBookFactory
    {
        public StudentBook GetFromId(int id)
        {
            return new StudentBookProxy()
                       {
                           Identity = id
                       };
        }
    }

Value Holder:

W tej wersji klient ma świadomość funkcjonalności leniwego ładowania, zatem usługi wywoływane są z pełną świadomością typu Value Holdera. Korzystając z dot Neta w wersji 3.5 lub wyższej można do tego celu stworzyć generyczny interfejs, który zwraca obiekt, np poprzez metodę Load. Implementacja metody load może być dokonana na wiele sposobów. Tworzy się także klasę generyczną, która zawiera propercję z obiektem, który nas będzie interesował.


public interface IValueLoader<T>
    {
        T Load();
    }

    public class ValueHolder<T>
    {
        private T _value;
        private readonly IValueLoader<T> _loader;

        public T Value
        {
            get
            {
                if (_value == null)
                    _value = _loader.Load();
                return _value;
            }
        }

        public ValueHolder(IValueLoader<T> loader)
        {
            _loader = loader;
        }
    }

    public class SimpleLoader : IValueLoader<List<Employee>>
    {
        public List<Employee> Load()
        {
            Thread.Sleep(100);
            return new List<Employee>();
        }
    }

    public class Employee
    {
        public int Salary { get; set; }
    }

    public class Salaries
    {
        public int Id { get; set; }

        public Salaries(int id)
        {
            Id = id;
        }

        private ValueHolder<List<Employee>> _employees;

        public List<Employee> GetEmployees()
        {
            return _employees.Value;
        }

        public void SetEmployees(ValueHolder<List<Employee>> loader)
        {
            _employees = loader;
        }
    }

    public class SalariesFactory
    {
        public Salaries GetItem()
        {
            var salaries = new Salaries(1);
            salaries.SetEmployees(
                new ValueHolder<List<Employee>>(new SimpleLoader()));
            return salaries;
        }

    }

Ghosts:

Duchem nazywany jest obiekt znajdujący się w stanie częściowym, początkowo obiekt taki zawiera jedynie Id. W momencie, kiedy odczytywana jest którakolwiek z propercji, obiekt ładuje cały swój stan. Można powiedzieć, że obiekt stanowi zatem wirtualne proxy sam dla siebie, przez co nie pojawiają się problemy z tożsamością obiektu. Obiekt może zatem znajdować się w trzech stanach : załadowany, w trakcie ładowania oraz duch. Implementację wzorca można pozostawić klasie abstrakcyjnej, natomiast reprezentację stanu wartości typu wyliczeniowego.


public class Customer : DomainObject
    {
        public Customer(int id) : base(id)
        {
            
        }

        private string _name;
        public string Name
        {
            get
            {
                Load();
                return _name;
            }
            set
            {
                Load();
                _name = value;
            }
        }

        private int _age;
        public int Age
        {
            get
            {
                Load();
                return _age;
            }
            set
            {
                Load();
                _age = value;
            }
        }

        private List<int> _orderCosts;
        public List<int> OrderCosts
        {
            get
            {
                Load();
                return _orderCosts;
            }
            set
            {
                Load();
                _orderCosts = value;
            }
        }

        public override ArrayList GetDataRow(int id)
        {
            //symulacja pobierania z bazy danych, np.
            //SELECT * FROM CUSTOMERS WHERE CustomerID = id
            var al = new ArrayList();
            al.Add("Marek Nowak");
            al.Add(31);
            al.Add(new List<int> {1, 7});
            return al;
        }

        public override void InjectLineIntoObject(ArrayList arrayList)
        {
            //Mapowanie row na propercje
            Name = arrayList[0] as string;
            Age = int.Parse(arrayList[1].ToString());
            OrderCosts = arrayList[2] as List<int>;
        }
    }

    public abstract class DomainObject
    {
        public int Id { get; set; }
        private LoadStatus Status { get; set; }

        protected DomainObject(int id)
        {
            Id = id;
        }

        public bool IsGhost
        {
            get { return Status == LoadStatus.Ghost; }
        }

        public bool IsLoading
        {
            get { return Status == LoadStatus.Loading; }
        }

        public bool IsLoaded
        {
            get { return Status == LoadStatus.Loaded; }
        }

        public void Load()
        {
            if (!IsGhost) return;
            Status = LoadStatus.Loading;
            var row = GetDataRow(1);
            InjectLineIntoObject(row);
            Status = LoadStatus.Loaded;
        }



        public abstract ArrayList GetDataRow(int id);
        public abstract void InjectLineIntoObject(ArrayList arrayList);

    }

    public enum LoadStatus
    {
        Ghost,
        Loading,
        Loaded
    }
Na koniec warto wspomnieć o klasie Lazy, która również zapewnia opisane powyżej mechanizmy. Opis tutaj

wtorek, 10 lipca 2012

[Wzorce projektowe] Flyweight

Analogia z życia:

Chodząc do szkoły codziennie rano pakujemy do plecaka zeszyty. Mając pięć godzin lekcyjnych, w tym dwie godziny języka polskiego, zabierzemy cztery zeszyty (prawdopodobnie częściowo już zapisane). Moglibyśmy dla każdej lekcji każdego przedmiotu zakładać nowy zeszyt, ale byłoby to kosztowne zupełnie niepotrzebne. Każda nowa lekcja jest identyfikowana przez stronę, na której się znajduje i przez rodzaj zeszytu w jakim jest zapisana. Oczywiście do różnych przedmiotów mamy do dyspozycji różne zeszyty (w linie, kratkę, gładkie). Zawsze jednak jeden zeszyt może nam posłużyć do zapisywania lekcji przez cały semestr z danego przedmiotu.



Zastosowanie:

Wzorzec Flyweight znajduje zastosowanie wszędzie tam, gdzie mamy do czynienia z dużą ilością obiektów tego samego typu, co wiąże się z dużymi kosztami pamięciowymi przy przechowywaniu. Dzięki niemu możemy używać tego samego obiektu w wielu sytuacjach, przy zachowaniu całej elastyczności związanej z korzystaniem z niego, co powoduje, że zamiast używać np. setek instancji możemy skorzystać zaledwie z kilku. Aby było to możliwe, musimy wyciągnąć z obiektów ich zewnętrzny stan poza obiekt, przy zachowaniu wewnętrznych właściwości. Dzięki temu aplikacja nie zależy od tożsamości konkretnych instancji obiektów, mimo iż podchodząc w 100% obiektowo do modelowania zagadnienia musielibyśmy utworzyć o wiele więcej obiektów.

Zasada działania:

Na początku tworzymy pewien interfejs reprezentujący rodzinę obiektów. Następnie modelujemy różnice w obiektach poprzez konkretne implementacje tego interfejsu. W miejscu, w którym pojawia się zapotrzebowanie na obiekty posługujemy się fabryką, która zwraca już istniejące instancje i wywołuje na nich pewne akcje. Zewnętrzne cechy obiektów przekazywane są przez parametr.

Przykład implementacyjny:

  public interface ISoldier
    {
        int Damage { get; set; }
        void Shoot(int x, int y);
    }

    public class LightInfantry : ISoldier
    {
        public LightInfantry()
        {
            Damage = 20;
        }

        public int Damage { get; set; }

        public void Shoot(int x, int y)
        {
            Console.WriteLine(String.Format
                ("Shooting from ({0},{1}), done {2} damage",x,y,Damage));
        }
    }

    public class Artillery : ISoldier
    {
        public Artillery()
        {
            Damage = 200;
        }

        public int Damage { get; set; }

        public void Shoot(int x, int y)
        {
            Console.WriteLine(String.Format
                ("Shooting from ({0},{1}), done {2} damage", x, y, Damage));
        }
    }
public class SoldierFactory
    {
        private Dictionary<string, ISoldier> _instances =
            new Dictionary<string, ISoldier>();

        
        public ISoldier GetSoldier(string typeName)
        {
            switch (typeName)
            {
                case "LightInfantry":
                    if (!_instances.ContainsKey(typeName))
                        _instances.Add(typeName, new LightInfantry());
                    return _instances[typeName];
                case "Artillery" :
                    if (!_instances.ContainsKey(typeName))
                        _instances.Add(typeName, new Artillery());
                    return _instances[typeName];
                default:
                    throw new NotImplementedException();
            }
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            CreateArmyAndAttack();
            Console.Read();
        }

        public static void CreateArmyAndAttack()
        {
            Random r = new Random();
            var factory = new SoldierFactory();
            for (int i = 0; i < 1000; i++)
            {
                factory.GetSoldier("LightInfantry")
                    .Shoot(r.Next() % 100, r.Next() % 100);
            }
            for (int i = 0; i < 100; i++)
            {
                factory.GetSoldier("Artillery")
                    .Shoot(r.Next() % 20, r.Next() % 100);
            }
        }
    }

[Wzorce projektowe] Event Aggregator

Analogia z życia: 


Nowe wydania gazet pojawiają się przeważnie każdego dnia, co tydzień lub np. co miesiąc. Kiedy tylko wydawnictwo przygotuje całe wydanie, wysyła je do kiosku. Czytelnik zainteresowany swoimi ulubionymi gazetami udaje się do kiosku by nabyć nowe egzemplarze. Informacje na temat tego, czy nowe wydanie już jest dostępne uzyskuje właśnie od kioskarza.



Zastosowanie:

Event Aggregator upraszcza zarządzanie zdarzeniami przez wprowadzenie jednego scentralizowanego obiektu zarządzającego nimi. Dzięki temu powiązania między nadawcami i odbiorcami eventów są zredukowane do minimum. Zatem wzorca tego należy użyć wszędzie tam, gdzie mamy do czynienia z wieloma modułami, które muszą się ze sobą komunikować, a także w każdym innym przypadku, gdzie relacja między nadawcami a odbiorcami zdarzeń przedstawia się jako wiele do wielu. Dodatkowo zmniejszone zostaje ryzyko wycieków pamięci związanych z eventami, a wprowadzenie nowego zdarzenia nie zaburza struktury całości.

Zasada działania:

W omawianym wzorcu występują trzy rodzaje obiektów : nadawcy, odbiorcy i agregator. Zarówno nadawcy, jak i odbiorcy przechowują referencję do agregatora. Gdy zajdzie taka potrzeba nadawcy wywołują odpowiednie metody z agregatora notyfikując odbiorców. Odbiorcy wołają metody subskrypcji by odbierać wiadomości, które ich interesują. Odbiorca może na przykład implementować generyczny interfejs, mówiący o tym, które eventy chce subskrybować. Event Aggregator przechowuje jedynie słabe referencje (takie, które nie powstrzymają GC przed sprzątnięciem obiektu) do subskrybentów, co zapobiega wyciekom pamięci.

Przykład implementacyjny:

 public interface IEventAggregator
    {
        void Subscribe(object subscriber);
        void Publish<TEvent>(TEvent eventToSend);
    }

    public class SimpleEventAggregator : IEventAggregator
    {
        private readonly Dictionary<Type, List<WeakReference>> _subscribersList = 
            new Dictionary<Type, List<WeakReference>>();

        private readonly object _syncObject = new object();

        public void Subscribe(object subscriber)
        {
            lock (_syncObject)
            {
                var subscriberTypes = subscriber.GetType().GetInterfaces().Where(i => i.IsGenericType
                                                                                      &&
                                                                                      i.GetGenericTypeDefinition() ==
                                                                                      typeof (ISubscriber<>));
                var weakReference = new WeakReference(subscriber);
                foreach (Type subscriberType in subscriberTypes)
                {
                    var subscribers = GetSubscribers(subscriberType);
                    subscribers.Add(weakReference);
                }
            }
        }

        private List<WeakReference> GetSubscribers(Type subscriberType)
        {
            List<WeakReference> subscribers;
            lock (_syncObject)
            {
                var found = _subscribersList.TryGetValue(subscriberType, out subscribers);
                if(!found)
                {
                    subscribers = new List<WeakReference>();
                    _subscribersList.Add(subscriberType, subscribers);
                }
            }
            return subscribers;
        }

        public void Publish<TEvent>(TEvent eventToSend)
        {
            var subscriberType = typeof (ISubscriber<>).MakeGenericType(typeof (TEvent));
            var subscribers = GetSubscribers(subscriberType);
            List<WeakReference> toRemove = new List<WeakReference>();

            foreach (WeakReference weakSubscriber in subscribers)
            {
                if(weakSubscriber.IsAlive)
                {
                    var subscriber = (ISubscriber<TEvent>) weakSubscriber.Target;
                    var syncContext = SynchronizationContext.Current;
                    if(syncContext == null)
                        syncContext = new SynchronizationContext();
                    syncContext.Post(s => subscriber.OnEvent(eventToSend), null);
                }
                else
                {
                    toRemove.Add(weakSubscriber);
                }
            }

            if(toRemove.Any())
            {
                lock (_syncObject)
                {
                    foreach (WeakReference weakReference in toRemove)
                    {
                        subscribers.Remove(weakReference);
                    }
                }

            }
        }
    }
 public interface ISubscriber<in T>
    {
        void OnEvent(T message);
    }

    public class StatsCounter : ISubscriber<ScoreMessage>, ISubscriber<AllPlayersFinished>
    {
        public static readonly object _sync = new object();
        private Dictionary<string, int> _players;

        public StatsCounter()
        {
            _players = new Dictionary<string, int>();
        }


        public void OnEvent(ScoreMessage message)
        {
            lock (_sync)
            {
                var name = message.Sender.Name;
                Console.WriteLine(String.Format("{0} scored !", name));
                if(_players.ContainsKey(name))
                {
                    _players[name]++;
                }
                else
                {
                    _players.Add(name,0);
                }
            }
        }

        public void OnEvent(AllPlayersFinished message)
        {
            lock (_sync)
            {
                Console.WriteLine("Results : ");
                foreach (KeyValuePair<string, int> keyValuePair in _players)
                {
                    Console.WriteLine(String.Format("{0}\t scored {1} time(s)"),
                        keyValuePair.Key, keyValuePair.Value);
                }
                Console.ReadKey();
            }
        }
    }

    public class GameManager : ISubscriber<FinishMessage>
    {
        private readonly IEventAggregator _ea;

        private int _counter;

        public GameManager(IEventAggregator eventAggregator)
        {
            _ea = eventAggregator;
        }

        public void OnEvent(FinishMessage message)
        {
            _counter++;
            lock (StatsCounter._sync)
            {
                Console.WriteLine(String.Format("{0} Finished ! <-----",message.Sender.Name));
                Console.ReadKey();
            }
            if(_counter == 3)
                _ea.Publish(new AllPlayersFinished());
        }
    }
public interface IPlayer
    {
        string Name { get; set; }
    }

    public class Player : IPlayer
    {
        private IEventAggregator _ea;

        public int Sum { get; set; }
        public string Name { get; set; }

        public Player(IEventAggregator eventAggregator, string name)
        {
            _ea = eventAggregator;
            Name = name;
        }

        public void Play()
        {
            Random r = new Random();
            while (Sum < 1000)
            {
                int num = (r.Next() % 20);
                Sum += num;
                if(Sum % 10 == 0)
                    _ea.Publish(new ScoreMessage{Sender = this});
                Thread.Sleep(10*num);
            }
            _ea.Publish(new FinishMessage() {Sender = this});
        }
    }
public class ScoreMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class FinishMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class AllPlayersFinished
    {
        
    }
class Program
    {
        static void Main(string[] args)
        {
            IEventAggregator eventAggregator = new SimpleEventAggregator();
            var player1 = new Player(eventAggregator, "Player 1 [POL]");
            var player2 = new Player(eventAggregator, "Player 2 [USA]");
            var player3 = new Player(eventAggregator, "Player 3 [RUS]");
            StatsCounter counter = new StatsCounter();
            eventAggregator.Subscribe(counter);
            GameManager manager = new GameManager(eventAggregator);
            eventAggregator.Subscribe(manager);

            Thread t1 = new Thread(player1.Play);
            Thread t2 = new Thread(player2.Play);
            Thread t3 = new Thread(player3.Play);
            
            Console.WriteLine("The game has started !");

            t1.Start();
            t2.Start();
            t3.Start();
        }
    }