Wzorce projektowe – fabryki

Factory_1Za każdym razem gdy używamy w kodzie operatora new, uzależniamy się od konkretnej implementacji zamiast od interfejsu. Jedna z zasad programowania obiektowego (reguła odwracania zależności / dependency inversion principle) mówi:

 

Uzależniaj kod od abstrakcji, a nie od klas rzeczywistych.

Zapewne każdy programista spotkał się ze składnią:

Wcześniej czy później z pewnością zmienią się wymagania i zajdzie konieczność rozbudowy programu. Nie obejdzie się bez modyfikacji istniejącego kodu. Wtedy zostanie złamana zasada open/closed:

System powinien być otwarty na rozbudowę, ale zamknięty na modyfikacje.

Można zauaważyć że fragmentem kodu mogącym ulegać zmianom będzie instrukcja warunkowa if/else zwracająca konkretny produkt w zależności od przekazywanego parametru. Ten fragment kodu nie jest zamknięty na zmiany, ponieważ gdy zajdzie konieczność dodania nowego produktu lub usunięcie istniejącego będziemy musieli wrócić do tego miejsca i wykonać aktualizację.

Rodzaje fabryk

To tyle tytułem wstępu. W poniższym wpisie opiszę różne rodzaje fabryk, które są pomocne w procesie tworzenia obiektów. Jest ich kilka wersji, ale omówię poniższe:

  1. Prosta fabryka (simple factory)
  2. Fabryka statyczna (static factory)
  3. Metoda fabrykująca (factory method)
  4. Fabryka abstrakcyjna (abstract factory)

1. Prosta fabryka (simple factory)

Spójrz na fragment kodu powyżej. Wyodrębnimy fragment odpowiedzialny za utworzenie konkretnego produktu do osobnej klasy, którą nazwiemy prostą fabryką:

Teraz pierwotna klasa zawierająca tą metodę będzie wyglądać następująco:

Najłatwiej zapamiętać, że:

Prosta fabryka wywoływana jest bezpośrednio przez klasę która chce utworzyć nowy obiekt. Wywołana metoda fabryki zwraca jeden z wielu obiektów dziedziczących po wspólnej klasie bazowej lub implementujących wspólny interfejs.

 

2. Fabryka statyczna (static factory)

Fabryka statyczna to nic innego, jak prosta fabryka posiadająca statyczną metodę do utworzenia produktu. Rozwiązanie ma taką zaletę, że nie jest konieczne tworzenie obiektu fabryki aby zwrócić konkretny produkt. Ma jednak taką wadę, że nie można zastosować wtedy dziedziczenia i obsługiwać przekazywanie dodatkowych parametrów w konstruktorze.

W stosunku do wcześniej zdefiniowanej prostej fabryki, fabryka statyczna będzie się różniła tylko słowem static w definicji metody tworzącej:

Prosta fabryka vs. statyczna

Fabryka statyczna ogranicza nas możliwością dziedziczenia. Załóżmy, że chcemy dodać możliwość utworzenia obiektu klasy Product na podstawie połączenia do bazy danych. Wykorzystując prostą fabrykę, możemy utworzyć nową klasę DbSimpleFactory dziedziczącą po SimpleFactory i przekazującej w konstruktorze obiekt połączenia z bazą danych. Możemy wtedy „wstrzyknąć” taką fabrykę do klasy naszego klienta ProductProcess, który nie będzie nawet nic wiedział, że wykorzystywane jest połączenie do bazy danych.

Wykorzystanie fabryki statycznej wymusiłoby na nas dodanie kolejnego parametru do metody, a tym samym klient ProductProcess musiałby również posiadać odwołanie do bazy danych. Złamana zostałaby kolejna zasada:

Staraj się, aby współpracujące obiekty były ze sobą luźno powiązane

 

3. Metoda fabrykująca (factory method)

Wzorzec ten definiuje dwie równoległe hierarchie klas (interfejsy):

  1. Klasy produktów (np. RoundProduct, SquareProduct).
  2. Klasy fabryki (np. PolishFactory, GermanFactory).

Metoda fabrykująca definiuje  interfejs pozwalający na tworzenie obiektów, równocześnie pozwalając klasom podrzędnym decydować jakiej klasy obiekt zostanie stworzony. Odpowiedzialność za tworzenie obiektów zostaje przekazana klasom podrzędnym.

Implementacja wzorca dla napisanej wcześniej klasy ProductProcess może wyglądać tak:

Metodą fabrykującą jest abstrakcyjna metoda:

Załóżmy, że chcemy teraz przygotować możliwość przetwarzania produktów w dwóch wersjach:

  1. Wersja dla lokalizacji w Polsce.
  2. Wersja dla lokalizacji w Niemczech.

Użyte składniki w procesie produkcji towarów mogą się różnić w zależności od kraju. W takim przypadku z pomocą przychodzi właśnie metoda fabrykująca.

Polska fabryka produktów:

Niemiecka fabryka produktów:

Przykład użycia:

Metoda fabrykująca pozwala klientom podrzędnym decydować jakiej klasy obiekt zostanie utworzony.

Zalety:

  • usunięcie zależności pomiędzy implementacją produktu, a jego zastosowaniem,
  • hermetyzacja procesu tworzenia obiektów w zdefiniowanym interfejsie,
  • możliwość wprowadzenia wielu metod tworzących obiekty na różne sposoby. Daje to większe możliwości i zwiększa czytelność kodu w porównaniu do tworzenia obiektów przez operator new (gdzie wszystkie przeciążone konstruktory mają tą samą nazwę, a dodanie kolejnego sposobu tworzenia wiązałoby się z dodaniem kolejnych parametrów do konstruktora).

 

4. Fabryka abstrakcyjna (abstract factory)

Ideą fabryki abstrakcyjnej jest tworzenie rodzin spokrewnionych ze sobą obiektów. Ten wzorzec tak jak poprzednie pozwalają na tworzenie kodu, który będzie uzależniony od abstrakcji, a nie od implementacji konkretnych klas.

Warto wiedzieć, że można zaimplementować ten wzorzec na kilka sposobów (w zależności od potrzeb):

  1. Fabryka abstrakcyjna obiektów tego samego typu.

    Założenie: ta wersja posiada metody tworzące obiekty tego samego typu które są od siebie niezależne (np. różne marki samochodów: Fiat, Audi). Aby stworzyć kolejną wersję produktu (np. BMW) należy rozszerzyć interfejs o nową metodę (np. createBMW). W takich przypadkach metody zwracające ten sam typ interfejsu warto zastąpić jedną metodą parametryzującą:

    Ta nowa funkcja jest szkieletem wcześniej opisanego wzorca metody fabrykującej i stosowanie go w fabrykach abstrakcyjnych jest powszechne.
  2. Fabryka abstrakcyjna rodziny zależnych od siebie obiektów.

    Ta wersja fabryki tworzy różne rodzaje produktów które są od siebie zależne aby powstał produkt finalny (składający się z obiektów klas ProductA i ProductB).
  3. Hybrydowa fabryka abstrakcyjna.

    Wersja hybrydowa łączy dwie poprzednie odmiany fabryki abstrakcyjnej.

Przykładowa implementacja

Załóżmy, że mamy do oprogramowania proces montażu części w komputerze: procesora i wiatraczka. Mamy dostępnych dwóch producentów: Intel oraz AMD, a każdy procesor musi zostać zamontowany z wiatraczkiem wyprodukowanym przez tego samego producenta.

Będziemy tutaj mieli trzy hierarchie abstrakcji:

  1. Producenci (jako fabryki)
  2. Procesory (jako produkty)
  3. Wiatraczki (jako produkty)

Wynik:

 

Należy zwrócić uwagę na fragment:

Aby utworzyć obiekt (komputer) składający się z innych zależnych od siebie obiektów (procesor, wiatraczek) wstrzykujemy do konstruktora odpowiednią fabrykę części.

Tym sposobem zabezpieczamy się przed przykładowym przypadkiem złożenia komputera składającego się z procesora Intel i wiatraczka AMD.

Przykład zastosowania z życia programisty:

Obsługa generowania raportów w wielu formatach (HTML, PDF, XLS). Każdy format posiada swoją własną fabrykę, która tworzy elementy takie jak: nowa strona, tabela, a tabela komórki tabeli. Tak więc każdy format zawiera rodzinę zależnych od siebie elementów (nie można przecież łączyć użycia komórek w formacie XLS i PDF w raporcie HTML).

Podsumowanie

  1. Prosta fabryka:
    1. Jest najprostszym sposobem na oddzielenie klienta od implementacji.
    2. Przeważnie wywoływana przez klienta przez statyczną metodę (przeważnie parametryzowaną) i zwraca jeden z wielu obiektów tego samego interfejsu.
  2. Metoda fabrykująca:
    1. Wykorzystuje mechanizm dziedziczenia.
    2. Implementacja polega na stworzeniu metody abstrakcyjnej która zostanie zaimplementowana w klasie po niej dziedziczącej.
  3. Fabryka abstrakcyjna:
    1. Wykorzystuje kompozycję.
    2. Zwraca całą rodzinę powiązanych ze sobą obiektów. Przeważnie wykorzystuje metodę fabrykującą do tworzenia obiektów.

To również może Cię zainteresować:

  • Wzorce projektowe – singletonWzorce projektowe – singleton Singleton - jeden ze wzorców konstrukcyjnych. Jego celem jest zapewnienie możliwości utworzenia tylko jednego obiektu danej klasy i zapewnienie do niego globalnego dostępu. Przez wielu […]
  • Wzorce projektowe – dekoratoryWzorce projektowe – dekoratory Dekorator to jeden se strukturalnych wzorców projektowych, dzięki któremu możemy wykorzystać kompozycję w alternatywie do dziedziczenia w celu rozszerzenia zachowania klasy. W przypadku […]
  • Fundamenty języka JavaFundamenty języka Java Niecały miesiąc temu zostałem poproszony przez Strefę Kursów o ocenę ich nowo wydanego materiału dla osób chcących zacząć naukę programowania w Javie: "Fundamenty języka Java". Przerobiłem […]
  • Zbiór przykładów programistycznych w różnych językachZbiór przykładów programistycznych w różnych językach Poniżej interesująca lista odnośników do rozwiązań kilkuset zadań programistycznych w różnych językach programowania: 1 100 doors 2 24 game 24 game/Solve 9 9 […]
  • Permutacje, cz. 2 – algorytmyPermutacje, cz. 2 – algorytmy W poprzednim wpisie programistyczne rozwiązanie zagadki polegało na wygenerowaniu wszystkich permutacji zbioru i sprawdzeniu każdej z nich pod względem spełnienia warunku […]
  • Java i listing wszystkich plików w kataloguJava i listing wszystkich plików w katalogu W ostatnim czasie musiałem przerobić jeden z systemów na wersję wielojęzykową. Chodziło dokładnie o to, aby wszystkie Stringi zostały wywołane przez tzw. wrapper ze wstrzyknięciem pewnego […]

2 thoughts on “Wzorce projektowe – fabryki

  1. Sporo w ostatnim czasie pisalem o wzorcach projektowych . Staralem sie w miare dokladnie zglebic ten temat i po stworzeniu kilku tekstow z tego cyklu, naszlo mnie na mala refleksje – wzorce projektowe sa nierozerwalnie zwiazane z polimorfizmem.

  2. Cześć!
    Mam takie pytanie: jak wyglądałaby implementacja w momencie gdy klasy poszczególnych procesorów miałyby pola z danymi (takie jak String czy int), a byłyby różne dla Intela i AMD? Chodzi mi głównie o implementacje klas fabrykujących.
    Pozdrawiam

Dodaj komentarz

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