niedziela, 28 marca 2010

Abstract Factory Pattern

W kolejnym poście poświęconym wzorcom projektowym, zajmiemy się rozszerzeniem wzorca Factory Method. W definicji tego wzorca możemy przeczytać: Abstract Factory udostępnia interfejs pozwalający na tworzenie rodzin produktów. Spójrzmy na diagram UML:

Interfejs AbstractFactory definiuje interfejs, który musi być implementowany przez konkretne fabryki tworzące produkty (obiekty).
Concrete Factories odpowiadają za utworzenie konkretnych produktów.
Abstract Product definiuje interfejs rodziny produktów.
Product - produkt żądany przez klienta.

Przejdźmy do przykładu:
Przyjmijmy że mamy dwie piekarnie. Jedna zlokalizowana jest w Krakowie druga w Warszawie. Obie pieką placki kruche i z owocami. Piekarnia w Krakowie piecze placek z jabłkami i śliwkami. Piekarnia w Warszawie piecze placki z wiśniami i truskawkami. Przyjrzyjmy się temu opisowi od strony projektanta oprogramowania. Klient który zamawia placki ma możliwość zamówienia placka od dwóch różnych dostawców (piekarnie Kraków i Warszawa). W każdej z piekarń produkuje się placki o różnych składach a to co je łączy, to to, że są to placki typu Kruchy i Owocowy. Te informacje są wystarczające do zbudowania wzorca Abstract Factory. (Dodałem jeszcze pole price zawierające cenę placka - nie używałem go jednak; można samemu dodać np. wyświetlanie ceny po wywołaniu metody opis).
A więc przejdźmy do sedna czyli kodu :) :

    1     public abstract class Bakery
    2     {
    3         public abstract IFruitPie CreateFruitPie();
    4         public abstract ITartPie CreateTartPie();
    5     }
    6 
    7     public class KrakowBakery : Bakery
    8     {
    9         public override IFruitPie CreateFruitPie()
   10         {
   11             return new ApplePie(23.50);
   12         }
   13 
   14         public override ITartPie CreateTartPie()
   15         {
   16             return new PlumPie(28.40);
   17         }
   18     }
   19 
   20     public class WarsawBakery : Bakery
   21     {
   22         public override IFruitPie CreateFruitPie()
   23         {
   24             return new CherryPie(33.68);
   25         }
   26 
   27         public override ITartPie CreateTartPie()
   28         {
   29             return new StrawberryPie(39.40);
   30         }
   31     }
   32 
   33     public interface IFruitPie
   34     {
   35         string Description();
   36         double Price { get; }
   37     }
   38 
   39     public interface ITartPie
   40     {
   41         string Description();
   42         double Price { get; }
   43     }
   44 
   45     public class PlumPie : ITartPie
   46     {
   47         private double _price;
   48 
   49         public PlumPie(double price)
   50         {
   51             this._price = price;
   52         }
   53         #region ITartPie Members
   54         public string Description()
   55         {
   56             return "Tart Plum Pie";
   57         }
   58 
   59         public double Price
   60         {
   61             get { return this._price; }
   62         }
   63 
   64         #endregion
   65     }
   66 
   67     public class StrawberryPie : ITartPie
   68     {
   69         private double _price;
   70 
   71         public StrawberryPie(double price)
   72         {
   73             this._price = price;
   74         }
   75         #region ITartPie Members
   76         public string Description()
   77         {
   78             return "Tart Strawberry Pie";
   79         }
   80 
   81         public double Price
   82         {
   83             get { return this._price; }
   84         }
   85 
   86         #endregion
   87     }
   88 
   89     public class ApplePie : IFruitPie
   90     {
   91         private double _price;
   92 
   93         public ApplePie(double price)
   94         {
   95             this._price = price;
   96         }
   97         #region IFruitPie Members
   98 
   99         public string Description()
  100         {
  101             return "Pie with fresh Apples";
  102         }
  103 
  104         public double Price
  105         {
  106             get { return _price; }
  107         }
  108 
  109         #endregion
  110     }
  111 
  112     public class CherryPie : IFruitPie
  113     {
  114         private double _price;
  115 
  116         public CherryPie(double price)
  117         {
  118             this._price = price;
  119         }
  120         #region IFruitPie Members
  121 
  122         public string Description()
  123         {
  124             return "Cherry Pie with fresh Cherries";
  125         }
  126 
  127         public double Price
  128         {
  129             get { return _price; }
  130         }
  131 
  132         #endregion
  133     }
  134 
  135     class Program
  136     {
  137         static void Main(string[] args)
  138         {
  139             Bakery bakery = new KrakowBakery();
  140             IFruitPie FruitPie = bakery.CreateFruitPie();
  141             Console.WriteLine(FruitPie.Description());
  142             ITartPie TartPie = bakery.CreateTartPie();
  143             Console.WriteLine(TartPie.Description());
  144 
  145             Console.WriteLine("---------");
  146 
  147             bakery = new WarsawBakery();
  148             FruitPie = bakery.CreateFruitPie();
  149             Console.WriteLine(FruitPie.Description());
  150             TartPie = bakery.CreateTartPie();
  151             Console.WriteLine(TartPie.Description());
  152         }
  153     }

Może kilka słów o implementacji.
1 - 5 - definicja abstrakcyjnej klasy Abstract Factory
7 - 31 - definicje konkretnych fabryk
33 - 43 - interfejsy odpowiedzialne za typy produktów
44 - 132 - definicje konkretnych produktów (placków)

Powyższa implementacja może zostać ulepszona poprzez np. użycie typów generycznych - dzięki temu tworzymy wzorzec do generowania konkretnych fabryk czy produktów (mniej pisania).

Kiedy stosować Abstract Factory?
- generowanie widoku i interfejsu aplikacji;
- interakcja z systemami operacyjnymi (w każdym mamy takie operacje jak otwórz, zapisz, wyświetl - tyle, że w Windowsie jest to inaczej realizowane niż np w Linuxie);
- kiedy znamy cały zestaw tworzonych produktów (dodanie nowego wymaga zmiany we wszystkich fabrykach)

Co zyskujemy dzięki Abstract Factory?
- łatwa zmiana całych "rodzin produktów" (np. zmiana skórek w aplikacji)
- ukrycie implementacji przed klientem
- logiczna spójność systemu - fabryka odpowiada za tworzenie odpowiednich produktów

Brak komentarzy:

Prześlij komentarz