Bing AI
Dzisiejszy odcinek jest sponsorowany przez Hermetyzacja z.o.o. Dołącz do naszej idei, a już nigdy nie będziesz musiał mieć jakiejkolwiek styczności z innymi klasami w realu! Po co brudzić sobie ręce produkcją obiektów skoro możesz to zlecić naszym fabrykom?
Wniosek płynący z opisu wzorca Dekorator w poprzednim wpisie mógłby brzmieć następująco:
Dziedziczenie jest be, interfejsy są cacy.
Coś w tym jest, ale nie popadajmy w przesadę. O ile sztywna struktura klasowa może się okazać problemem przy późniejszej rozbudowie aplikacji, o tyle w schludny sposób deklaruje hierarchię klas i upraszcza kod. Mimo to pisząc super- i podklasy warto zadbać o wspomnianą we wstępniaku hermetyzację czyli odseparowanie elementów mających tendencję do zmian od reszty kodu.
Ale po czym poznamy takie elementy? Cóż, jedną z wskazówek może być słówko:
new
W programowaniu obiektowym musimy tworzyć nowe obiekty, to fakt. Abstrakcja klas i interfejsów pomaga w ułożeniu dobrze działającej struktury, ale koniec końców musimy i tak stworzyć jakiś obiekt, nadać mu pożądany stan i wywołać jego metody. Tylko, że można to zrobić na rympał, a można to zrobić z głową – z pomocą fabryki.

Tym razem zajmiemy się jednocześnie dwoma wzorcami, które stoją blisko siebie, zazwyczaj są używane razem i jednocześnie tak często są ze sobą mylone, że grzechem byłoby je rozdzielać na dwa artykuły. A są to:
Abstract Factory oraz Factory Method
Dwa wzorce z fabryką w nazwie. Znaczy – muszą coś produkować. A co? Spytajmy podręcznik:
Wzorzec Metoda Fabrykująca definiuje interfejs pozwalający na tworzenie obiektów, ale pozwala klasom podrzędnym decydować, jakiej klasy obiekt zostanie utworzony. Wzorzec MF przekazuje odpowiedzialność za tworzenie obiektów do klas podrzędnych.
Rusz Głową! Wzorce projektowe.
Wzorzec Fabryka Abstrakcyjna dostarcza interfejs do tworzenia całych rodzin spokrewnionych lub zależnych od siebie obiektów bez konieczności określania ich klas rzeczywistych.
W moim pierwszym projekcie stworzyłem klasę SquareFactory posiadającą jedną statyczną metodę createColoredSquares(…) która tworzyła dla mnie listę z żądaną ilością kwadracików w wybranym kolorze:
public static List<Square> createColoredSquares(int amount, SquareColor color) {
List<Square> coloredSquares = new ArrayList<>();
for (int i = 0; i < amount; i++) {
String name = color.name().toLowerCase() + i;
Square square = new Square(color, name);
coloredSquares.add(square);
}
return coloredSquares;
}
Nie był to jednakże przykład wykorzystania ani Factory Method ani Abstract Factory, a co najwyżej idiom czyli taki mini wzorzec, który może ograniczać się do jednej linijki kodu, która już na pierwszy rzut oka jest zrozumiała. Do pełnego wzorca trochę zabrakło.
Ale po kolei…
Jedną z najbardziej skutecznych, a jednocześnie najłatwiejszych taktyk w grach na silniku Infinity Engine (czyli takich jak Wrota Baldura albo Icewind Dale) jest przywoływanie stworzeń, tzw. „summonów” aby walczyły za nas.
Kluczem jest posiadanie w drużynie klas, które mają czar przyzwania – takich jak np. czarodziej, kleryk albo druid. Wtedy w zależności od tym kim kierujemy, możemy liczyć na powiększenie naszej drużyny o kilka wilków, żywiołaka ognia albo – na najwyższych poziomach profesji – o najprawdziwszego anioła („Devę”)!

Maksymalna ilość summonów jednocześnie to 5. Niektóre czary przyzywają od razu kilku słabszych pomagierów, z kolei inne skupiają się na przywołaniu jednego, ale za to kozaka. Mając całą piątkę takich jednorazowych minionów wysyłamy ich w rejon mapki gdzie wcześniej zauważyliśmy wrogów i czekamy patrząc na logi w dzienniku. Summony, tak jak wrogowie, mają swoje skrypty więc nie musimy im wydawać żadnych rozkazów – atakują najbliższego wroga póki nie padnie, albo same nie zginą. Kiedy akcja zelżeje wpadamy na pobojowisko swoją drużyną, likwidujemy niedobiktów i zbieramy fanty.
Klimatyczne? W żadnym wypadku!
Skuteczne? Jeszcze jak!
A teraz pomyślmy – jak najlepiej tworzyć obiekty summonów mając na uwadze, że zależą one od klasy postaci i jej poziomu oraz użytego zaklęcia? A do tego są to elementy jednorazowe, bez znaczenia dla fabuły.
Tego się nie tyka, to zrobiła fabryka.
Należy posłużyć się fabrykami, które odseparują takie szybkie tworzenie obiektów (gdzie liczy się ilość, a nie jakość) od reszty kodu. Spójrzcie poniżej, cały kod znajdziecie tutaj.


Mamy dwie klasy: Wizard oraz Druid, z tym samym interfejsem: Summoner. Nie tworzyłem tutaj osobnych zaklęć aby nie komplikować kodu. Każda klasa korzysta z metody summon(), ale z wykorzystaniem własnej fabryki. Obie zaimplementowane fabryki są podklasami abstrakcyjnej SummonFactory (czyli naszej ABSTRACT FACTORY).

Fabryka ta posiada dwie metody – jedna (summonAllies) jest już zaimplementowana i „dzieci” klasy używają jej dokładnie w takiej formie, bez przesłaniania. Natomiast druga (createSummons) jest abstrakcyjna i to od podklas zależy co tam dokładnie się znajdzie.

Podsumowując. Abstract factory (AF) zajmuje się tworzeniem rodziny spokrewnionych obiektów – otrzymujemy listę „summons” tylko z obiektami implementującymi owy interfejs. Factory method (FM) to pusta metoda w tej fabryce – to od podklas zależy w jaki sposób zostanie zaimplementowana.
Powyższy opis wciąż może wydawać się niejasny, dlatego poratuję was jeszcze hintem:
AF musi odpowiadać na pytanie „ile obiektów dostaniemy?”, natomiast FM na „jakie dokładnie to będą obiekty?”.
W przytoczonym kodzie AF mówi, że dostaniemy listę z iluśtam przyzwańcami. A FM tłumaczy, że jeśli jesteśmy czarodziejem to dostaniemy żywiołaka, a jak druidem to wilki.
Przy okazji – przytoczony problem równie dobrze dałoby się rozwiązać wzorcem Strategia i tak ja to właśnie zrobiłem w swoim projekcie. Jak się teraz okazuje – trochę na opak, bo zgodnie z zasadą Strategia proponuje różne algorytmy do otrzymania konkretnych danych, a wzorce Fabryki specjalizują się w tworzeniu obiektów. Niby detal, ale wiele mówi innym programistom co się dzieje w danych klasach już na pierwszy rzut oka.
ITCandidateEvaluator
Abstract factory oraz Factory method przydają się szczególnie kiedy masowo tworzymy obiekty – czy to podczas kompilacji czy już w czasie działania programu. W moim przypadku pasuje do tego szablonu obiekt „KandydatDoPracy” (Candidate). Zakładając, że na daną ofertę dostaniemy ponad 100 zgłoszeń, z czego sporo odpadnie już na etapie przeglądania CV to i tak można liczyć na kilkanaście rozmów rekrutacyjnych co przekłada się na taką samą liczbę obiektów Candidate.
Sam obiekt kandydata nie będzie szczególnie skomplikowany. Na pewno musi mieć zmienne takie jak: imię i nazwisko, może jeszcze datę urodzenia. Przyda się ścieżka do pliku z CV. Ponadto mapa „punkty” z punktami otrzymanymi na każdym etapie rekrutacji oraz metoda calculatePoints() zawierająca algorytm wyliczający końcowy wynik.
I tu właśnie zaczynają się schody. No bo tak, mogę po prostu stworzyć klasę Candidate, ustawić wszystko na sztywno i mieć resztę w du… Ale potem może się okazać, że algorytm liczący punktację dla juniora może się różnić od tego dla seniora. I zmieniaj chłopie teraz te wszystkie przypadki użycia obiektu Candidate… Lepiej niech Candidate to będzie klasa abstrakcyjna po której dziedziczą Juniorzy, Midzi i Seniorzy. Albo nie, bo może kiedyś…
Sami widzicie – sprawę należy przemyśleć bardzo dokładnie. Jedno jest natomiast pewne:
Projekt jest rozwojowy, zadbam więc o odpowiednią hermetyzację, a to oznacza, że miejsce dla jakiejś fabryki na pewno się znajdzie.
Dziękuję za uwagę i zapraszam do następnego wpisu poświęconego wzorcowi Singleton.