Budowniczy w akcji!

Dzisiaj porozmawiamy trochę o jednym z bardziej popularnych wzorców projektowych, a mianowicie o Budowniczym (Builder). Jest to jeden z kreacyjnych wzorców projektowych.

A na co to komu?

Dzięki użyciu budowniczego możemy z pewnością osiągnąć przynajmniej dwie rzeczy:

  • oddzielimy tworzenie obiektu od jego reprezentacji,
  • nasz kod powinien stać się trochę bardziej przyjazny dla oka 😉

The good old way…

Na początku zobaczmy jak będzie wyglądał zwykły obiekt POJO. Jako przykład stwórzmy klasę, która będzie zawierała wszystkie informacje o mailu, który będzie mógł być później wysłany.

Jak widzimy, nasza klasa ma za zadanie przechowanie informacji o tym do kogo mail będzie wysłany (to), dodatkowych odbiorów (cc), temat maila (subject) oraz jego treść (content).

Stwórzmy teraz instancje naszej klasy:

Co prawda ten kod nie wygląda jeszcze zbyt strasznie, ale wyobraźmy sobie teraz, że do naszej klasy dodajemy jeszcze kilka pól jak bcc, mime type, załączniki i jeszcze kilka innych. Wtedy nasza klasa trochę się rozrośnie i stworzenie instancji tej klasy nie będzie wyglądało zbyt przyjemnie, szczególnie mając na uwadze, że dużo pól naszej klasy ma ten sam typ.

A co jeśli…

Możemy się okazać, że część pól w naszej klasie nie jest obowiązkowa przy tworzeniu naszego obiektu. W naszym przykładzie pole cc nie musi być obowiązkowe. Co w takim przypadku powinniśmy zrobić?

Możemy stworzyć dodatkowy konstruktor bez pola cc 🙂 Tylko co jeśli pól opcjonalnych będziemy mieć kilka? Tworzenie wszystkich kombinacji konstruktorów zdecydowanie odpada.

W takim przypadku możemy przecież stworzyć POJO z setterami/getterami 🙂

I stwórzmy teraz instancje naszej klasy, tylko tym razem bez inicjowania pola cc:

Podejście to pozwala nam stworzyć instancje naszego obiektu tylko z wybranymi przez nas polami. Dzięki setterom jest również trudniej o pomyłkę związaną z kolejnością przekazywanych parametrów do konstruktora.

Czy można zrobić to ładniej?

Wydaję mi się, że można 😉 I właśnie budowniczy przyjdzie nam z pomocą 🙂

Budowniczy pomaga nam w tworzeniu skomplikowanych obiektów poprzez danie możliwości tworzenia (budowania) ich kawałek po kawałku. Stwórzmy zatem budowniczego do naszej klasy 🙂

Zmiany, których dokonaliśmy w naszej klasie to:

  • stworzyliśmy prywatny konstruktor do klasy Mail, aby nikt nie stworzył nam naszego obiektu na lewo ;),
  • dodaliśmy statyczną metodę builder(), która zwraca nową instancje budowniczego,
  • dodaliśmy samego budowniczego.

Klasa budowniczego zawiera w sobie pole z instancją budowanego obiektu, metody odpowiadające ustawianym polom oraz metodę build(), która wieńczy dzieło i zwraca nam naszą zbudowaną instancje klasy Mail. Zwróćmy uwagę, że metody budowniczego zwracają instancje budowniczego. Dzięki czemu możemy zbudować nasz obiekt w sposób ‚płynny’ (fluent interface) 😉

I w ten oto sposób udało nam się stworzyć prostego budowniczego 🙂

A co jeśli… (cz. 2)

Wcześniej zauważyliśmy, że część pól z naszej klasy może być opcjonalna. Warto, żebyśmy też popatrzyli w drugą stronę – część pól z może być konieczna do poprawnego zainicjowania naszego obiektu. W naszej klasie Mail możemy uznać, że pola to, subject i content są obowiązkowe. I co teraz?

The good old way jeszcze raz 😉

Wróćmy na sekundę do podejścia z POJO. Problem ten możemy tutaj rozwiązać poprzez stworzenie konstruktora, który będzie przyjmować obowiązkowe parametry oraz stworzeniem setterów dla parametrów opcjonalnych.

Tylko w tym podejściu znów powraca problem z wieloma parametrami w konstruktorze, które nie wyglądają zbyt ładnie oraz dodatkowo nie jest trudno o pomyłkę.

I wróćmy do budowniczego

A jak możemy ten problem rozwiązać w budowniczym?

Możemy obowiązkowe parametry przekazać do konstruktora budowniczego, ale oczywiście rezygnujemy z tej opcji 😉

Do rozwiązania tego problemu musimy trochę zmodyfikować naszego budowniczego, którego już stworzyliśmy.

Na początku stwórzmy sobie kilka prostych interfejsów.

Zauważmy, że dla każdego obowiązkowego pola stworzyliśmy interfejs, który zawiera dokładnie jedną metodę odpowiadającą za ustawienie tego pola. Zwróćmy uwagę też, że metoda ustawiające dane pole zwraca interfejs do ustawienia kolejnego obowiązkowego pola. Wyjątkiem jest interfejs ContentStep, który ustawia ostatnie obowiązkowe pole i zwraca interfejs OptionalSteps, który zawiera możliwość ustawienia wszystkich opcjonalnych pól oraz metodę build(), która zwraca nam gotowy obiekt.

Teraz musimy użyć tych interfejsów w naszym budowniczym.

Sama implementacja znacząco się nie zmieniła. Teraz nasz builder implementuje stworzone przez nas interfejsy. Musieliśmy też zmienić zwracane typy w metodach budowniczego.

I ostatnia zmiana, którą musieliśmy zrobić to zmiana typu zwracanego w metodzie tworzącej naszego budowniczego, żeby zwracała interfejs do pierwszego obowiązkowego pola.

 

Samo budowanie obiektu nie zmieniło się w porównaniu do tego, jak było w naszym pierwszym budowniczym, ale dzięki tym kilku interfejsom klient używający naszego budowniczego będzie musiał podać wszystkie obowiązkowe pola, żeby utworzyć obiekt.

I to jest właśnie to, co chcieliśmy osiągnąć 🙂