Czytasz jeden z artykułów opisujących wzorce projektowe. jeżeli interesuje Cię ten temat zapraszam Cię do lektury pozostałych artykułów, które powstały w ramach tej serii – wzorce projektowe. W zrozumieniu artykułu przyda Ci się wiedza dotycząca podstaw UML’a.
Problem do rozwiązania
Wyobraź sobie sytuację, w której prowadzisz sklep internetowy ze znaczkami pocztowymi. Obsługa zamówień odbywa się przez program, który zarządza całym procesem. Program nadzoruje wszystko od złożenia zamówienia do obsługi ewentualnych reklamacji. Jednym z etapów obsługi zamówienia jest wysyłka towaru do klienta.
Do tej pory program pozwalał wyłącznie na wysyłkę znaczków używając standardowej poczty. Z biegiem czasu klienci zaczęli oczekiwać dostępności innych sposobów dostawy. Problem polega na tym, iż program używa wyłącznie jednego rodzaju wysyłki. Z pomocą w usprawnieniu takiego programu może przyjść metoda wytwórcza (ang. factory method).
W tym przypadku metoda wytwórcza może być odpowiedzialna za tworzenie klas odpowiedzialnych za różne rodzaje wysyłek.
Wzorzec projektowy metoda wytwórcza
Diagram klas
Ten wzorzec projektowy w jednej ze swoich form opiera się o 4 elementy. Proszę spójrz na diagram klas poniżej:
- Product – klasa bazowa dla obiektów tworzonych przez metodę wytwórczą,
- Creator– klasa zawierająca metodę wytwórczą factoryMethod,
- SublassedProduct – przykładowa podklasa Product,
- SubclassedCreator – podklasa, nadpisująca metodę wytwórczą zwracając instancję SubclassedProduct.
Chociaż na diagramie klas pokazałem Product jako klasę, w rzeczywistości wcale nie musi tak być. Podobnie metoda factoryMethod nie musi być abstrakcyjna.
Product może być zdefiniowany jako interfejs. W takim przypadku podklasy Creator tworzą instancje różnych klas implementujących interfejs Product. Metoda factoryMethod wcale nie musi być abstrakcyjna. Klasa Creator może mieć domyślną implementację tej metody, która może być napisana przez podklasy.
Inną modyfikacją może być wprowadzenie parametrów do metody wytwórczej. W takim przypadku parametry mogą mieć wpływ na obiekt, który jest przez nią zwracany.
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Przykładowa implementacja metody wytwórczej
Java
W przykładzie odpowiednikiem Product będzie następujący interfejs:
Interfejs ten jest implementowany przez kilka klas. Jedną z nich możesz zobaczyć poniżej:
Odpowiednikiem klasy Creator jest klasa OrderLifecycle, która obsługuje cykl życia zamówienia. Jak widzisz poniżej metoda wytwórcza zwraca instancję PostOffice:
Dodatkowe podklasy nadpisują implementację metody wytwórczej zwracając inną implementację interfejsu DeliveryService:
Przykładowa metoda main pokazuje sposób wywołania poszczególnych klas, które używają metody wytwórczej:
Po uruchomieniu tego programu na konsoli pokaże się:
Python
Implementacja w języku Python wygląda trochę prościej1:
Efekt działania tego programu będzie dokładnie taki sam jak w przypadku implementacji w języku Java.
Dodatkowe rozważania
Metoda wytwórcza to specyficzny przypadek innego wzorca projektowego – metody szablonowej. Wzorzec metody szablonowej opiszę w jednym z kolejnych artykułów w serii.
Metoda wytwórcza może być częścią innego wzorca projektowego jakim jest fabryka abstrakcyjna, także ten wzorzec omówię w jednym z kolejnych artykułów w serii.
Zalety
Stosowanie metody wytwórczej sprawia, iż kod staje się łatwiejszy do testowania. Dzieje się tak ponieważ w łatwy sposób można nadpisać metodę wytwórczą używając mock’ów, albo naiwnej implementacji na potrzeby testów.
To, iż kod jest łatwiejszy do testowania jest konsekwencją stosowania reguł opisanych przez akronim SOLID:
- kod jest możliwy do rozszerzania – tworząc podklasy w bardzo łatwy sposób możesz zmienić zachowanie klas używających metody wytwórczej,
- możesz używać obiektów podklas zwracanych przez metody wytwórcze – to zachowanie to „serce” metody wytwórczej.
Wady
Moim zdaniem główną wadą tego wzorca projektowego jest hierarchia dziedziczenia. Prowadzi ona do powstawania wielu (nadmiarowych?) bytów. Przeciążenie metody wytwórczej wymaga dziedziczenia po klasie, która ma już implementację tej metody. Pewną alternatywą dla takiego podejścia może być stosowanie kompozycji zamiast dziedziczenia. Proszę spójrz na przykład:
To rozwiązanie nie jest już „czystą” metodą wytwórczą. To coś pomiędzy budowniczym (tak, kolejny wzorzec, który opiszę w innym artykule) a metodą wytwórczą. Na byt tego typu czasami mówi się po prostu fabryka.
Przykłady użycia wzorca metody wytwórczej
Ten wzorzec projektowy jest często używany w ramach fabryki abstrakcyjnej. Za przykład może to posłużyć metoda LogFactory.getLog z biblioteki commons-logging.
Innymi przykładami mogą być metody w fabrykach związanych z obsługą formatu JSON, na przykład JsonReaderFactory czy JsonBuilderFactory.
Zadanie do wykonania
W sekcji opisującej wady metody wytwórczej pokazałem sposób modyfikacji tego wzorca projektowego. Zaimplementuj analogiczne rozwiązanie w języku Java. Spróbuj użyć wyrażeń lambda. Przydatny może też być interfejs Supplier.
Dodatkowe materiały do nauki
Niezmiennie, we wszystkich artykułach z serii poświęconej wzorcom projektowym polecam książkę Design Patterns – Gamma, Helm, Johnson, Vlissides. jeżeli miałbym polecić wyłącznie jedno źródło to poprzestałbym na tej książce.
Warto także rzucić okiem do polskiej i angielskiej Wikipedii, gdzie znajdziesz artykuły opisujące metodę wytwórczą:
- artykuł na polskiej Wikipedii o metodzie wytwórczej,
- artykuł na angielskiej Wikipedii o metodzie wytwórczej.
Kod źródłowy przykładów użytych w artykule także może być pomocny:
Podsumowanie
Wiesz już czym jest metoda wytwórcza i jak można ją zbudować. Znasz przykłady jej zastosowania zarówno z przykładu w artykule jak i innych bibliotek. Poznałeś zalety i wady tego wzorca projektowego. Wiesz jak można poradzić sobie z jego wadami. jeżeli udało Ci się samodzielnie rozwiązać zadanie do wykonania możesz śmiało powiedzieć, iż znasz ten wzorzec projektowy. Gratulacje! :)
Jeśli artykuł przypadł Ci do gustu proszę podziel się nim ze znajomymi. Dzięki temu pozwolisz mi dotrzeć do nowych Czytelników, za co z góry dziękuję. jeżeli nie chcesz pomiąć kolejnych artykułów dopisz się do samouczkowego newslettera i polub Samouczka Programisty na Facebooku.
Do następnego razu!
-
Jeśli coś chodzi jak kaczka i kwacze jak kaczka to jest kaczką ;). W odróżnieniu od Javy nie stosowałem tu dziedziczenia w przypadku odpowiedników klasy Product. Tę implementację można ją jeszcze uprościć, jak pokazałem paragrafie opisującym wady. ↩