wzorzec projektowy budowniczy

Wzorzec projektowy Budowniczy


Wprowadzenie

Budowniczy (Builder) jest kreacyjnym wzorcem projektowym. Jego głównym zadaniem jest tworzenie złożonych obiektów etapami, krok po kroku. Najczęściej używamy go w przypadku, gdy klasa ma bardzo dużo pól i nie chcemy dla niej tworzyć dużej liczby konstruktorów.

Problem

Tworzymy system, który pozwala budować użytkowników z różnymi konfiguracjami konta. Każde konto ma różne domyślne ustawienia (rola, uprawnienia, dostęp do funkcji, itp.).

Uwaga: w przykładzie klasa User ma niewiele pól, żeby zachować czytelność. W rzeczywistych systemach obiekty mogą mieć wiele pól i złożone konfiguracje, wtedy użycie wzorca Budowniczy jest bardziej uzasadnione.

Bez zastosowania wzorca projektowego tworzylibyśmy obiekty wprowadzając wszystkie domyślne ustawienia do konstruktora, przez co konstruktor byłby rozbudowany, a czasami nie wszystkie parametry byłyby potrzebne za każdym razem. Innym rozwiązaniem byłoby utworzenie wielu różnych konstruktorów, które różniłyby się ilością przyjmowanych pól. Oba rozwiązania utworzyłyby niezły bałagan i zawirowanie w kodzie klasy.

Rozwiązanie

Z pomocą przychodzi wzorzec Budowniczy, który proponuje umieszczenie kodu konstrukcyjnego w osobnych obiektach zwanych budowniczymi. Ponadto pozwala na budowanie obiektu krok po kroku. Co więcej, wykorzystując interfejs, można utworzyć wiele klas, które będą implementowały metody na swój sposób.

Opcjonalnie można użyć kierownika, który ukrywa sekwencję wywołań budowniczego oraz kontroluje kolejność wykonywania kroków budowy.

Implementacja

Struktura wzorca

Głównym elementem poniższego diagramu UML jest interfejs UserBuilder. Deklaruje on publiczne metody umożliwiające ustawienie wartości odpowiednich pól oraz metodę getUser(), która zwraca gotowego użytkownika.

Występuje dwóch budowniczych – RegularUserBuilder oraz AdminUserBuilder – którzy implementują ten interfejs i definiują szczegóły budowy obiektu. Oczywiście nic nie stoi na przeszkodzie, aby utworzyć więcej budowniczych, odpowiadających innym konfiguracjom użytkownika.

Proces budowy nadzoruje kierownik UserDirector, który w konstruktorze przyjmuje obiekt typu UserBuilder. Dzięki temu może wywołać odpowiednie metody budowniczego w określonej kolejności (etapowanie budowy), a następnie zwrócić gotowy obiekt.

wzorzec projektowy budowniczy

Struktura plików

  • builder
    • user.py
    • builder.py
    • director.py
    • main.py

Kod źródłowy

user.py

builder.py

director.py

main.py

Wynik działania:

Podsumowanie działania

Na powyższym przykładzie za pomocą kodu klienckiego najpierw utworzono budowniczego dla zwykłego użytkownika, kolejno ustanowiono kierownika, który za pomocą budowniczego utworzył nowego użytkownika o nazwie mateusz. Analogiczny proces wystąpił w przypadku budowniczego dla admina.

Taki sposób budowania obiektów niesie ze sobą wiele korzyści. Umożliwia zróżnicowanie wewnętrznych struktur budowanych obiektów, dzięki czemu możemy otrzymać obiekty tego samego typu, ale o różnych konfiguracjach. Dodatkowo pozwala kontrolować proces tworzenia – kierownik decyduje o kolejności wykonywania kroków przez budowniczego. Co więcej, wydzielenie skomplikowanego kodu konstrukcyjnego do osobnej klasy budowniczego oraz etapowania do klasy kierownika sprawia, że klient nie musi przejmować się tymi szczegółami. Dzięki temu spełniamy zasadę Single Responsibility (pierwszą zasadę SOLID).

Kiedy nie używać wzorca

Wzorzec Budowniczy nie zawsze jest najlepszym rozwiązaniem. Nie warto go stosować, gdy:

  • tworzony obiekt ma niewiele pól,
  • konstrukcja obiektu jest prosta i nie wymaga etapowania,
  • nie istnieje wiele wariantów konfiguracji,
  • nie zależy nam na kontroli kolejności budowy,
  • obiekt można w czytelny sposób skonfigurować przez parametry konstruktora lub wartości domyślne.

W takich przypadkach zastosowanie wzorca może prowadzić do nadmiernego rozbudowania struktury projektu oraz wprowadzenia niepotrzebnej liczby klas.