Pokazywanie postów oznaczonych etykietą design. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą design. Pokaż wszystkie posty

środa, 11 listopada 2015

[Wzorce projektowe] Visitor

Analogia z życia:

Ludzie mieszkają w mieszkaniach, które zazwyczaj są zamykane przed innymi ludźmi. Czasami zdarzają się jednak sytuacje, gdy ktoś obcy musi wejść do naszego mieszkania, np. żeby naprawić kran. Wtedy otwieramy mieszkanie dla takiej osoby, udostępniamy mu wybraną jego część (kuchnię) i pozwolimy, by wykonał odpowiednie czynności.




Zastosowanie:

Wszędzie tam, gdzie chcemy wydzielić jakąś operację, wykonywaną na obiekcie danego typu poza definicję tego typu. Jeżeli obiekt dobrze implementuje enkapsulację, to nikt z zewnątrz nie może dostać się do jego stanu, a jednocześnie operacje na tym stanie może wykonywać specjalny typ - visitor.

Zasada działania:

Definiuje się dwie rodziny obiektów (rodziny rozumiane jako klasa bazowa plus jej implementacje), elementy oraz wizytorzy. Element to obiekt zawierający strukturę danych i udostępniający operację Visit przyjmującą jako parametr obiekt z rodziny wizytorów. Visitor i elementy po nim dziedziczące reprezentują akcje wykonywane na obiektach typu Element. Visitor wystawia metodę Accept przyjmującą elementy dziedziczące po klasie Element i wykonujące na niej dane operacje.

Definicja grupy elementów:

public abstract class Element
{
    public abstract void Accept(Visitor visitor);
}

public class Tap : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitKitchen(this);
    }
}

public class Shower : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitBathroom(this);
    }
}

Definicja wizytatora:

public abstract class Visitor
{
    public abstract void VisitKitchen(Tap tap);
    public abstract void VisitBathroom(Shower shower);
}

public class Plumber : Visitor
{
    public override void VisitBathroom(Shower shower)
    {
        Console.WriteLine("Repairing " + nameof(shower));
    }

    public override void VisitKitchen(Tap tap)
    {
        Console.WriteLine("Repairing " + nameof(tap));
    }
}

Definicja obiektu mieszkania, akceptującego "wizytę":

public class Flat
{
    private List<Element> _stuff = new List<Element>();

    public void Attach(Element element)
    {
        _stuff.Add(element);
    }

    public void Detach(Element element)
    {
        _stuff.Remove(element);
    }

    public void Accept(Visitor visitor)
    {
        foreach (Element element in _stuff)
        {
            element.Accept(visitor);
        }
    }
}

static void Main(string[] args)
{
    var flat = new Flat();
    flat.Attach(new Tap());
    flat.Attach(new Shower());

    flat.Accept(new Plumber());

    Console.ReadKey();
}

poniedziałek, 9 listopada 2015

[Wzorce projektowe] Composite

Analogia z życia:

Oddając samochód do mechanika powierzamy mu kluczyki i oczekujemy naprawy zepsutych części. Mechanik po odebraniu kluczyków wjeżdża samochodem na warsztat i zleca swoim pracownikom konkretne zadania: wymianę zderzaka, prostowanie blachy, lakierowanie. Z naszego punktu widzenia załatwiamy wszystkie te sprawy z jedną osobą (kierownikiem warsztatu), nie musimy martwić się o to, kto będzie wykonywał poszczególne zadania.



Zastosowanie:

Wszędzie tam, gdzie chcemy, aby kod klienta wiedział jak najmniej o procesie, który woła. Klient powinien być niezależny od tego procesu i przechowywać jedynie referencję do obiektu kompozytu, który dba o to, by wywołać odpowiednie konkretne składowe procesu.

Zasada działania:

Klient zawiera referencję na pewien interfejs. Interfejs ten jest implementowany przez konkretne typy, jak również przez specjalny typ - kompozyt, który zawiera także kolekcję elementów implementujących interfejs.


public interface ICarMechanic
{
    void Repair(Car instance);
}

public class Tinsmith : ICarMechanic
{
    public void Repair(Car instance)
    {
        instance.CrackedBody = false;
    }
}

public class WheelChanger : ICarMechanic
{
    public void Repair(Car instance)
    {
        instance.WorkingWheels = 4;
    }
}

public class BumperChanger : ICarMechanic
{
    public void Repair(Car instance)
    {
        instance.HasNewBumper = true;
    }
}

//composite
public class CarService : ICarMechanic
{
    private List<ICarMechanic> _mechanics;

    public CarService(IEnumerable<ICarMechanic> mechanics)
    {
        _mechanics = new List<ICarMechanic>(mechanics);
    }

    public void Repair(Car instance)
    {
        if (instance.BrokenWheel)
        {
            RepairIfAny<WheelChanger>(instance);
        }

        if (instance.BrokenBody)
        {
            RepairIfAny<WheelChanger>(instance);                
        }

        if (instance.BrokenWheel)
        {
            RepairIfAny<BumperChanger>(instance);
        }
    }

    private void RepairIfAny<T>(Car instance) where T : ICarMechanic
    {
        var mechanic = _mechanics.OfType<T>().FirstOrDefault();
        if (mechanic != null)
            mechanic.Repair(instance);
    }
}

Wyekstraktowanie logiki napraw do klasy - kompozytu znacznie ułatwia kod po stronie klienta wołającego usługę naprawy.

public class CarOwner
{
    private Car _car;
    private ICarMechanic _mechanicContact;

    public CarOwner(Car car, ICarMechanic contact)
    {
        _car = car;
        _mechanicContact = contact;
    }

    public void RenewCar()
    {
        _mechanicContact.Repair(_car);
        Console.WriteLine("Car has been repaired");
    }
}

Cały proces startuje przez stworzenie odpowiednich zależności dla kompozytu:

static void Main(string[] args)
{
    var mechanicsSvc = new CarService(
        new ICarMechanic[] { new BumperChanger(), new WheelChanger(), new Tinsmith() });

    var car = new Car() { BrokenBody = true, BrokenWheel = true, HasNewBumper = false };

    var owner = new CarOwner(car, mechanicsSvc);
    owner.RenewCar();

    Console.ReadKey();
}

sobota, 20 października 2012

[HTML|JS|CSS] JavaScript Patterns: Wzorce projektowe

Wzorce projektowe znane z języków obiektowych nie zawsze są łatwe do zaimplementowania w języku JavaScript. Aby uzyskać podobny efekt, często należy znać pewne specyficzne konstrukcje JS. Poniżej przedstawiono trzy wzorce przez tzw Gang of Four
. Ich implementacja w JS jest nieco inna od tej, którą wszyscy znają z języków obiektowych.

  • Singleton

Jeżeli chcemy zapewnić, aby pewien obiekt był zinstancjonowany tylko raz, można skorzystać z faktu, że funkcja jest obiektem, który może posiadać własne properties.

function MyObject(){

 if(typeof MyObject.instance !== "undefined"){
  return MyObject.instance;
 }

 this.name = "MyObject";
 this.counter = 0;

 MyObject.instance = this;
}

var o1 = new MyObject();
o1.counter++;
var o2 = new MyObject();
console.log(o2.counter);

Program wypisze 1. Problemem jest, że statyczna instancja jest publiczna, aby tego uniknąć można skorzystać z konstrukcji closure.

  • Factory

Wzorzec przydatny przy tworzeniu całych rodzin obiektów. Ma on na celu umożliwienie podjęcia decyzji o tym, który dokładnie obiekt ma zostać stworzony, podczas pracy programu. JavaScript ułatwia implementację wzorca, ponieważ warto pamiętać, że obiekty są parami typu klucz - wartość.

function VehicleMaker(){}

VehicleMaker.prototype.printType = function(){
 console.log(this.type);
};

VehicleMaker.factory = function(type){
 var constr = type,
  newvehicle;

 if(typeof VehicleMaker[constr] !== "function"){
  throw {
   name: "Error",
   message: constr + " not found"
  };
 }

 if(typeof VehicleMaker[constr].prototype.printType !== 
  "function"){
  VehicleMaker[constr].prototype = new VehicleMaker();  
 } 
 newvehicle = new VehicleMaker[constr]();
 return newvehicle;
};

VehicleMaker.Car = function(){
 this.type = 'car';
};

VehicleMaker.Bike = function(){
 this.type = 'bike';
};

VehicleMaker.Bus = function(){
 this.type = 'bus';
};

var bus = VehicleMaker.factory('Bus');
var car = VehicleMaker.factory('Car');
var bike = VehicleMaker.factory('Bike');

bus.printType();
car.printType();
bike.printType();

try{
 var whatever = VehicleMaker.factory('whatever');
}
catch(err){
 console.log(err);
}

  • Decorator

Wzorzec pozwala rozszerzać w pewien sposób jakiś obiekt w czasie pracy programu, bez z góry narzuconych reguł. Każdy obiekt po dekoracji zachowuje swoje właściwości plus dodatkowo dostaje coś ekstra. W przypadku języka JS do implementacji można wykorzystać listę dekoratorów i odpowiednią metodę, która wykona ich zadania gdy przyjdzie na to czas.

function Degree(name){
 this.name = name;
 this.decorators_list = [];
}

Degree.decorators = {};

Degree.decorators.Engineer = {
 getDegree: function(name){
  return "inz. " + name;
 }
};

Degree.decorators.Doctor = {
 getDegree: function(name){
  return "dr " + name;
 }
};

Degree.decorators.Professor = {
 getDegree: function(name){
  return "Prof. " + name;
 }
};

Degree.decorators.Print = {
 getDegree: function(name){
  console.log(name);
 }
};

Degree.prototype.decorate = function(decorator){
 this.decorators_list.push(decorator);
};

Degree.prototype.getDegree = function(){
 var fname = this.name,
  i,
  max = this.decorators_list.length,
  name;
 for(i = 0; i < max; i+=1){
  name = this.decorators_list[i];
  fname = Degree.decorators[name].getDegree(fname); 
 }
 return fname;
};

var prof = new Degree("Jan Kowalski");
prof.decorate('Engineer');
prof.decorate('Doctor');
prof.decorate('Professor');
console.log(prof.getDegree());

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);
            }
        }
    }