Kiedy stosuje się wzorzec fabryki i budowniczego?
Info o tym można znaleźć w necie lub choćby w klasycznej książce Bandy Czterech "Wzorce projektowe".
Fabryki abstrakcyjnej używamy, gdy mamy kilka rodzin powiązanych ze sobą, ale oddzielnych produktów i chcemy uniezależnić się od tego, której rodziny aktualnie używa nasza aplikacja.
Książkowy przykład to fabryka widżetów interfejsu graficznego. Chcemy mieć możliwość tworzenia różnie wyglądających widżetów dla Windowsa, a różnych dla Linuxa.
Tworzymy więc abstrakcyjną klasę FabrykęWidżetów
. Ma ona metody abstrakcyjne takie jak Przycisk stwórzPrzycisk()
oraz Okno stwórzOkno()
, gdzie Okno
i Przycisk
to abstrakcyjne klasy widżetów. W całej aplikacji używamy właśnie tych metod naszej fabryki, by tworzyć widżety.
W pewnym miejscu inicjalizujemy naszą FabrykęWidżetów
instancją jakiejś konkretnej klasy-fabryki. Mamy dwie konkretne klasy: FabrykaWidżetówWindows
oraz FabrykaWidżetówLinux
.
Pierwsza z nich może być zdefiniowana w taki sposób:
class FabrykaWidżetówWindows implements FabrykaWidżetów {
@Override
public Przycisk stwórzPrzycisk() {
return new PrzyciskWindows();
}
@Override
public Okno stwórzOkno() {
return new OknoWindows();
}
}
Wewnątrz metod konkretnej fabryki mamy ukrytą konstrukcję obiektów dla konkretnego systemu. Tworzymy klasy konkretne takie jak OknoWindows
, ale reszta aplikacji widzi tylko obiekty abstrakcyjne typu Okno
-- jest więc chroniona przed tym, w jakim systemie aplikacja jest używana.
Na początku działania programu możemy sobie oczywiście wybrać, jaką fabrykę utworzymy.
Budowniczego używamy inaczej. Gdy budujemy jeden, duży (stosunkowo), skomplikowany produkt. Przy czym możemy budować jeden z kilku różnych produktów, byleby wszystkie je robiło się w tych samych krokach. Na Wikipedii ( http://en.wikipedia.org/wiki/Builder_pattern ) widzę ciekawy przykład z pizzą. Każdą pizzę robi się w tych samych trzech krokach i Budowniczy ma trzy metody: wypieczCiasto()
, połóżDodatki()
oraz polejSosem()
. Zawsze wykonujemy je w tej samej kolejności, ale pizza Hawajska będzie miała cienkie ciasto, ananasy i łagodny sos, a pizza inferno może mieć grube ciasto, papryczki chilli i ostry sos.
Banda Czterech, z tego co pamiętam, pokazuje nieco inny przykład, jak dla mnie superciekawy. Tworzenie labiryntów. Tworzący labirynty kod ma referencję do abstrakcyjnego BudowniczegoLabiryntów
. Wywołuje na nim metody takie jak int dodajKomnatę()
czy void dodajDrzwi(int nr_komnaty_1, int nr_komnaty_2)
, lub nawet void dodajPułapkę(Pułapka p, int nr_komnaty)
.
Fajne jest to, że fragmentowi kodu, który tworzy labirynt używając BudowniczegoLabiryntów, możemy podsuwać różnych budowniczych. Jasne, możemy mu dać normalnego budowniczego, którego metody faktycznie dodają do labiryntu komnaty, drzwi i pułapki, i który udostępnia utworzony labirynt poprzez metodę Labirynt dajUtworzonyLabirynt()
. Ale możemy też podsunąć ZliczającegoBudowniczegoLabiryntów
. Jego metody niczego nie tworzą. One tylko zliczają swoje wywołania: ile kto "utworzył" komnat, drzwi i pułapek. Kod, który korzysta z naszego budowniczego nawet nie wie, że żyje w matrixie i że podsunęliśmy mu tylko "symulację" tworzenia labiryntów, a w rzeczywistości tylko szpiegujemy i patrzymy, jaki labirynt byłby stworzony, budując sobie nasze statystyki.
Które wzorce projektowe są najbardziej przydatne i najczęściej używane?
Masz bana na Google?
Google niepotrzebne, całkiem niedawno była o tym mowa tutaj: Jakie wzorce projektowe stosujecie najczęściej ?
Zresztą, wątpię, żeby w Google była odpowiedź na pytanie, które wzorce są najbardziej przydatne i najczęściej używane, bo to jest tak subiektywna kwestia, że odpowiedzi nie ma - no chyba, że ktoś zrobił jakieś badania statystyczne wśród programistów.
Załóżmy, że wzorzec A i B są wykorzystywane najczęściej. Jaką wartość ma dla Ciebie ta informacja?
@Tezcatlipoca:
Zdarzyło mi się, ale niestety nie mogę podlinkować tego kodu: część jest closed source, a część jest cholera wie gdzie. Nie użyłem go jednak tak elegancko jak Banda Czterech.
W ciągu ostatniego roku tworzyłem np. budowniczych budujących odtwarzacze wideo na www, na podstawie różnych, zewnętrznych, często hostowanych serwisów. Krokami budowania były np. wygenerowanie niezbędnego HTML-a, rozpoczęcie ładowania bibliotek i odpalenie playera na HTML-u po załadowaniu bibliotek.
W innym projekcie, budowniczowie służyli to wygenerowania reguł walidacji formularzy. Jeden budowniczy konkretny korzystał z gotowej, znanej biblioteki walidacyjnej, ale było to zamknięte wewnątrz niego, a więc reszta aplikacji była niezależna od używanej biblioteki (nie nazwałbym tego jednak adapterem).
Budowniczy jest całkiem przydatny. Nawet gdy nie mamy (jeszcze?) kilku budowniczych konkretnych, a po prostu tworzymy jakiś złożony obiekt, wymagający skomplikowanej inicjalizacji. Zamiast konstruktora z wieloma parametrami, czytelniej czasem użyć obiektu budującego. Na moje oko, to i wtedy obiekt budujący złożone obiekty może być nazwany budowniczym, choć formalnie rzecz biorąc Budowniczy to klasa abstrakcyjna, która powinna mieć przynajmniej jedną implementację.
Wiki słusznie też wspomina, że w Budowniczowie często mają cechy "fluet interface" ( http://en.wikipedia.org/wiki/Fluent_interface ), co może zwiększyć czytelność kodu.
Ten mój przykład z walidacją formularzy zrobiłem głównie z uwagi na czytelność. Zastosowałem tam funkcyjne, monadowe API, dzięki któremu budowane reguły walidacji czytało się prawie jak język naturalny.