Abstract Factory – wzorce projektowe

W poniższym wpisie zajmę się kolejnym ze wzorców projektowych, a więc Fabryką Abstrakcyjna (ang. Abstract Factory).

Abstract Factory

jest wzorcem projektowych z grupy wzorców konstrukcyjnych. Dostarcza on interfejs za pomocą którego możliwe będzie tworzenie powiązanych ze sobą lub zależnych od siebie obiektów jednego typu (rodziny) bez określania ich konkretnych klas. Założenia tego wzorca są zbliżone do innego ze wzorców – Budowniczego (ang. Builder) z tą różnicą, że budowniczy koncentrował się na sposobie tworzenia obiektu, natomiast fabryka abstrakcyjny skupia się na tworzeniu produktów w konkretnej rodziny.

 

Składowe:

  1. AbstractFactory – jest interfejsem udostępniającym operacje służące do tworzenia obiektów abstrakcyjnych
  2. ConcreteFactory – implementuje operacje służące do tworzenia konkretnych produktów klasy AbstractFactory
  3. AbstractProduct – deklaruje interfejs dla obiektów danego typu
  4. ConcreteProduct – implementuje interfejs klasy AbstractProduct
  5. Client – korzysta jedynie z udostępnionych mu abstrakcyjnych interfejsów (AbstractProduct AbstractFactory)

Poniżej diagram obrazujący powiązania pomiędzy poszczególnymi elementami:

 

Zastosowanie:

  • Kiedy program powinien być niezależny od sposobu tworzenia, składania i reprezentowania jego produktów.
  • Jeżeli system powinien być konfigurowalny za pomocą jednej z dostępnych rodzin.
  • Jeżeli powiązane ze sobą obiekty wymuszają pracę z innymi obiektami danej rodziny.

Zalety: 

  1. Ukrywa klasy konkretne przed klientem. Klient może manipulować egzemplarzami tych klas jedynie poprzez udostępniony mu interfejs.
  2. Łatwa możliwość zastępowania rodzin produktów. Ma to miejsce ponieważ klasa fabryki konkretnej pojawia się w aplikacji tylko raz. W każdej chwili można zmienić wywoływaną fabrykę konkretną na inna, a jest to proste ponieważ jej nazwa występuje tylko w jednym miejscu w aplikacji.
  3. Zachowanie spójności pomiędzy produktami jednej rodziny. Jeżeli nasze produkty są zaprojektowane tak aby współpracowały z innymi produktami naszej rodziny, to możemy to w prosty sposób wymusić.

Wady:

  1. Problematyczne jest rozszerzenie fabryki abstrakcyjnej o nowe produkty. Wynika to z tego, że modyfikacja klasy AbstractFactory niesie za sobą konieczność modyfikacji wszystkich jej podklas.

 

A teraz trochę praktyki

  1. Zaczynamy od stworzenia sobie abstrakcyjnej fabryki (AbstractFactory), która po typie komputera będzie wybierała odpowiednią fabrykę (ConcreteFactory), oraz jedną metodę tworzącą nasz abstrakcyjny komputer (AbstractProduct).
    namespace AbstractFactory
    {
        abstract class ComputerFactory
        {
            public static ComputerFactory getFactory(ComputerType computerType)
            {
                switch (computerType)
                {
                    case ComputerType.notebook:
                        return new NotebookFactory();
    
                    case ComputerType.pc:
                        return new PcFactory();
                    default:
                        throw new NotImplementedException();
                }
            }
    
            public abstract Computer createComputer();
        }
    }
    
  2. Żeby wszystko przestało nam się świeć na czerwono należy stworzyć abstrakcyjną klasę naszego produktu (AbstractProduct) z metodą która wyświetli nam informacje
    namespace AbstractFactory.AbstractProduct
    {
        abstract class Computer
        {
            public abstract void showInfo();
        }
    }
    
  3. Pora na nasze konkretne fabryki (ConcreteFactory), które implementują naszą fabrykę abstrakcyjną i zwracają interesujący nas produkt
    namespace AbstractFactory.ConcreteFactory
    {
        class PcFactory : ComputerFactory
        {
            public override Computer createComputer()
            {
                return new PcComputer();
            }
        }
    }
    
    namespace AbstractFactory.ConcreteFactory
    {
        class NotebookFactory : ComputerFactory
        {
            public override Computer createComputer()
            {
                return new NotebookComputer();
            }
        }
    }
    
  4. Kolej na zajęcie się naszymi konkretnymi produktami (ConcreteProduct). Klasa naszego produktu implementuje klasę naszego abstrakcyjnego produktu (AbstractProduct). Tutaj też definiujemy produkt/obiekt.
    namespace AbstractFactory.Product
    {
        class NotebookComputer : Computer
        {
            public override void showInfo()
            {
                Console.WriteLine("New notebook");
            }
        }
    }
    
    namespace AbstractFactory.Product
    {
        class PcComputer : Computer
        {
            public override void showInfo()
            {
                Console.WriteLine("New PC Computer");
            }
        }
    }
    
  5. Wreszcie czas odpalić nasz program i zobaczyć jak cały mechanizm działa. Tworzymy obiekt fabryki i w moim przykładzie jako parametr przekazujemy typ interesującego nas komputera. Teraz metoda createComputer() powinna zwrócić produkt z interesującej nas rodziny.
    namespace AbstractFactory
    {
        class Program
        {
            static void Main(string[] args)
            {
                ComputerFactory factory = ComputerFactory.getFactory(ComputerType.notebook);
                Computer computer = factory.createComputer();
                computer.showInfo();
                Console.WriteLine();
                factory = ComputerFactory.getFactory(ComputerType.pc);
                computer = factory.createComputer();
                computer.showInfo();
            }
        }
    }
    

Na koniec efekt końcowy

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *