Posts Tagged ‘ Wzorce projektowe w C#

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

Wzorce projektowe w C# – Intro.

W ostatnim czasie zgłębiam tajniki wzorców projektowych wspomagając się tą oto pozycją książkową:

designpatternsbook.jpg

Książka w przemawiający do mnie sposób opisuję implementację dość dużej liczby wzorców projektowych opierając się na nowej wersji języka C#. Rozdziały kończą się ćwiczeniami sprawdzającymi poziom zrozumienia kolejnych wzorców. Niebawem spróbuje zaprezentować własną implementację niektórych ćwiczeń – wcześniej jednak na dobry początek przykład wzorca projektowego „Singleton” w połączeniu z wzorcem projektowym „Facade”.

Utworzyłem dwa Systemy których funkcjonalność jest udostępniania przez klasę Facade.

Klasa SystemA:

internal class SystemA
    {
        internal void OperationA()
        {
            Console.WriteLine("Operacja SystemA.");
        }
    }

Klasa SystemB:

internal class SystemB
    {
        internal void OperationB()
        {
            Console.WriteLine("Operacja SystemB.");
        }
    }

Klasa Facade:

/// <summary>
    /// Wzorzec projektowy Singleton
    /// Klasa bedaca jednoczesnie reprezentacja
    /// wzorca projektowego Facade
    /// udostepnia operacja poszczegolnych systemow
    /// </summary>
    public sealed class Facade
    {
        /// <summary>
        /// Obiekt pierwszego systemu
        /// </summary>
        private SystemA _systemA;
        /// <summary>
        /// Obiekt drugiego systemu
        /// </summary>
        private SystemB _systemB;

        /// <summary>
        /// Prywatny konstruktor
        /// </summary>
        private Facade()
        {
            //Utworzenie obiektow
            _systemA = new SystemA();
            _systemB = new SystemB();
        }

        /// <summary>
        /// Zmienna statczyna klasy Facade
        /// Jest utworzona w pierwszej kolejnosci
        /// </summary>
        private static readonly Facade _uniqueInstance = new Facade();

        /// <summary>
        /// Wlasciwosc zwracajaca unikalna instancje klasy
        /// </summary>
        public static Facade UniqueInstance
        {
            get
            {
                return _uniqueInstance;
            }
        }

        /// <summary>
        /// Operacja wykonywana przez jeden z Systemow
        /// SystemA
        /// </summary>
        public void Operation1()
        {
            _systemA.OperationA();
        }

        /// <summary>
        /// Operacja wykonywana przez jeden z Systemow
        /// SystemB
        /// </summary>
        public void Operation2()
        {
            _systemB.OperationB();
        }
    }

Tak oto wygląda wykorzystanie powyższej funkcjonalności:

//Utworzenie fasady systemow
Facade facade = Facade.UniqueInstance;
//Uruchomienie funkcjonalnosci
facade.Operation1();
facade.Operation2();

I to było by na tyle, kończę małą dawką humoru: „Każdego eksperta da się zastąpić skończoną liczbą studentów.” ;)