Dżemik z egzotycznych owoców – Abstract Factory :)

Zgodnie z zapowiedzią prezentuję własną interpretację jednego z zadań kończącego rozdział książki p.t. „C# 3.0 Design Patterns.”. Rozdział ten dotyczył wzorca projektowego „Abstract Factory”.

Delikatny zarys tego czym chcę się podzielić:

  • Krótko o tworzeniu własnych typów generycznych w C#
  • Zadanie, opis wzorca
  • Implementujemy, produkujemy (jemy i sprzedajem ;))

Typy generyczne:

W języku C# mamy możliwość tworzenia własnych typów generycznych.

class MyGenericType<T>
{
      private T _t;
}

Powyższa klasa daje możliwość zdefiniowania typu na jakim będzie mogła operować. Dodatkowo mamy możliwość nadawania ograniczeń na typy podawane podczas inicjalizacji obiektu.

 class MyGenericType<T> where T : IOperation, new()
{
        private T _t;
}

Ograniczenie to oznacza, że typ na którym będzie operowała klasa „MyGenericType” musi implementować interfejs IOperation, oraz jest zobowiazana posiadać jawnie zadeklarowany konstruktor.

Zadanie i opis wzorca:

Jeden z rozdziałów książki traktował o wzorcu projektowy „Factory Method” – przykład prezentujący działanie wzorca był opisem zdjęcia obrazującego źródło dostaw owoców awokado. Owoce te były dostarczane z różnych krajów Afryki. Zadanie kończące rozdział o wzorcu projektowym „Abstract Factory” odnosi się właśnie do tego zdjęcia – Należy wykorzystać „Abstract Factory” i opisać poniższą sytuację.

abstactfactory.png

Abstract Factory jest to wzorzec udostępniający instancje obiektów z istniejącej rodziny klas (Mam tutaj na myśli klasy dziedziczące po wspólnej klasie, wspólnym interfejsie). Obiekty te są odizolowane od klienta nimi zainteresowanego, co daje możliwość ich wymiany podmieniając tylko ich fabrykę. „Abstract Factory” to wzorzec który odpowiada za to co jest tworzone, a nie jak jest tworzone.

Implementujemy, produkujemy (jemy i sprzedajem ;))

Postanowiłem puścić wodzę fantazji i rozpocząłem budowę fabryki od zdefiniowania jej odpowiedzialności. Powstał genericsowy interfejs wraz z poniższymi ograniczeniami.

Interfejs „IAfricaFactory”:

interface IAfricaFactory<AfricaFruit>
        where AfricaFruit : IAfricaFruit, new()
{
     IInfoFruit GetNorthAfricaFruit();
     IInfoFruit GetSouthAfricaFruit();
     IOperationFruit GetROSA();
     IOperationFruit GetSudan();
}

Poszczególne metody będą zwracać instancję obiektów które implementują takie oto dwa interfejsy.

Interfejs „IInfoFruit”:

 interface IInfoFruit
 {
     string PriceFruit { get; }
     Quality QualityFruit { get; }
}

Interfejs „IOperationFruit”:

 interface IOperationFruit
 {
     string Marmalade();
     string OnSale();
}

Fabryka ma dostarczać i przetwarzać owoce nim jednak rozpocząłem ich tworzenie powstał interfejs zlecający im odpowiedzialność :)

Interfejs „IAfricaFruit”

interface IAfricaFruit
{
     string Price { get; }
     Quality QualityFruit { get; }
     string Marmelade();
     string OnSale();
}

Owoce:

Klasa „Avocado”:

class Avocado : IAfricaFruit
{
     #region IAfricaFruit Members
     public string Price
     {
        get
        {
            return "10 zl za kilogram";
        }
     }

     public Quality QualityFruit
     {
         get
         {
             return Quality.Druga;
         }
     }

     public string Marmelade()
     {
         return "Dżemik z avocado! :)";
     }

     public string OnSale()
     {
          return "Dżemik z owoców avocado sprzedajemy po: " + Price;
     }
     #endregion

     public override string ToString()
     {
          return "Avocado";
     }
}

Podobnie powstawały klasy „Banana” oraz „Lemon”. Następnie stworzyłem konkretną fabrykę(klasę) generyczną implementującą interfejs „IAfricaFactory”. Metody tworzą instancję klas implementujących intefejsy „IInfoFruit” oraz „IOperationFruit”.

Klasa „AfricaFactory”

class AfricaFactory<AfricaFruit> : IAfricaFactory<AfricaFruit>
        where AfricaFruit : IAfricaFruit, new()
{
     #region IAfricaFactory<AfricaFruit> Members
     public IInfoFruit GetNorthAfricaFruit()
     {
         return new NorthAfrica<AfricaFruit>();
     }

     public IInfoFruit GetSouthAfricaFruit()
     {
         return new SouthAfrica<AfricaFruit>();
     }

     public IOperationFruit GetROSA()
     {
         return new RepublicOfSouthAfrica<AfricaFruit>();
     }

     public IOperationFruit GetSudan()
     {
         return new Sudan<AfricaFruit>();
     }
     #endregion
}

Moja fabryka potrzebowała już tylko „kontaktu” z konkretnymi częściami bądź krajami Afryki. :)

Klasa „NorthAfrica”:

class NorthAfrica<AfricaFruit> : IInfoFruit
        where AfricaFruit : IAfricaFruit, new()
{
     private AfricaFruit _africaFruit;

     public NorthAfrica()
     {
           _africaFruit = new AfricaFruit();
     }
     #region IInfoFruit Members
     public string PriceFruit
     {
          get
          {
               return _africaFruit.Price;
          }
     }

     public Quality QualityFruit
     {
          get
          {
               return _africaFruit.QualityFruit;
          }
     }
     #endregion
}

Klasa implementuje interfejs „IInfoFruit”. Właściwości spełniające wymagania interfejsu „sięgają” do obiektu którego typ zostanie określony podczas tworzenia egzemplarza tej oto klasy, jednak jest pewne, że dostarczony typ musi implementować interfej „IAfricaFruit” oraz musi zawierać zadeklarowany jawnie konstruktor. Obiek dostarczanego typu tworzę w konstruktorze klasy „NorthAfrica”

Klasa „RepublicOfSouthAfrica”:

class RepublicOfSouthAfrica<AfricaFruit> : IOperationFruit
        where AfricaFruit : IAfricaFruit, new()
{
     private AfricaFruit _africaFruit;

     public RepublicOfSouthAfrica()
     {
          _africaFruit = new AfricaFruit();
     }

     public string Marmalade()
     {
          return _africaFruit.Marmelade();
     }

     public string OnSale()
     {
            return _africaFruit.OnSale();
     }
}

Klasa implementuje interfejs „IOperationFruit”. Tak samo jak w klasie „NorthAfrica” sięgam do zwartości typu który zostanie dostarczony podczas tworzenia egzemplarza klasy. Podobnie powstały klasy „SouthAfrica” (implementuje interfejs „IInfoFruit”) oraz „Sudan” (implementuje interfejs „IOperationFruit”). Mam już wszystko czego potrzebuję. Pozostaję tylko sprzedawać i jeść pyszny dżemik z owoców afrykańskich. ;)

Wywołanie:

//Fabryka Avocado
IAfricaFactory<Avocado> factoryOfBanana = new AfricaFactory<Avocado>();
//Dostarczam instancje obiektu "NorthAfrica"

IInfoFruit northAfrica = factoryOfBanana.GetNorthAfricaFruit();
//Wykorzystuje dostępne wlasciwosci
//aby sprawdzic cene i jakosc owocow

Console.WriteLine(northAfrica.PriceFruit);
Console.WriteLine("Jakość owoców: {0}", northAfrica.QualityFruit);
 //20zl za kilogram
//Jakosc owocow: Pierwsza

IOperationFruit RPA = factoryOfBanana.GetROSA();
//Po ile sprzedajemy :)
string p = RPA.OnSale();
Console.WriteLine(RPA.OnSale());
//Dżemik!!
string d = RPA.Marmalade();
Console.WriteLine(RPA.Marmalade());

//Dzemik z owocow avocado sprzedajemy po: 10 zl za kilogram
 //Dzemik z avocado! :)

Do fabryki możemy dostarczać kolejne owoce (klasy). Ważne jest aby każdy dostarczony typ do utworzonej przy użyciu typów generycznych fabryki implementował interfejs IAfricaFruit. :) Miłego analizowania. ;)

  1. Na razie brak komentarzy.

  1. Na razie brak trackbacków