Wprowadzenie

Istnieje wiele zasad programowania, którymi kierują się programiści, aby ich kod był jasny i zrozumiały dla innych. Programiści kierują się zasadami SOLID, które opisałam w innych artykułach oraz wzorcami projektowymi. Wzorce projektowe pomagają w utrzymaniu, tworzeniu oraz modyfikacji kodu źródłowego. Pokazują powiązania i zależności jakie występują pomiędzy klasami oraz obiektami.

W tym artykule chciałabym opisać wzorzec Builder. Wzorzec budowniczy jest jednym z popularnych wzorców projektowych stosowanych w programowaniu obiektowym. Jest to wzorzec kreacyjny, który umożliwia wieloetapowe tworzenie obiektu. Tworzenie obiektu według tego wzorca wykonywane jest przez obiekt budujący.

Cel

Głównym celem wzorca Builder jest umożliwienie tworzenia obiektów krok po kroku (etapami). Pozwala to na separację procesu tworzenia obiektu od samego obiektu, co z kolei ułatwia zarządzanie różnymi konfiguracjami obiektów oraz ich tworzenie (używając tego wzorca można produkować różne typy oraz różne reprezentacje obiektu nie zmieniając kodu konstrukcyjnego).

Kiedy używać wzorca Builder?

Wzorca Builder powinniśmy użyć gdy:

  • obiekt, który tworzymy jest złożony, nie można go uprościć
  • nie możemy utworzyć instancji obiektu poprzez jednorazową inicjalizację
  • obiekt będzie budowany wiele razy w różny sposób
Struktura wzorca Builder

Wzorzec Budowniczy składa się z kilku głównych komponentów:

  • Produkt – reprezentuje obiekt, który ma zostać zbudowany. Może to być klasa abstrakcyjna lub interfejs, który definiuje cechy i właściwości budowanego obiektu.
  • Budowniczy – interfejs lub klasa abstrakcyjna definiująca kroki potrzebne do budowy obiektu. Może zawierać metody do ustawiania różnych właściwości obiektu.
  • Konkretny budowniczy – klasy implementujące interfejs budowniczego. Realizują konkretne kroki konstrukcyjne. Każda z tych klas może zbudować obiekty o różnych właściwościach.
  • Dyrektor – komponent opcjonalny. Nadzoruje proces budowania obiektu. Może określać kolejność kroków konstrukcyjnych.
Różne rodzaje Burgerów

Wyobraźmy sobie, że chcemy zrobić burgera. Najprostszy burger składa się z bułki, sera, sałaty, kotleta. Co jeśli chcemy stworzyć „Big Maca” – potrzebujemy bułki, dwóch mięsnych kotletów, cebuli, ogórka, specjalnego sosu.

Co możemy zrobić?

Klasa Burger (Produkt)

  • Klasa Burger reprezentuje produkt (naszego burgera)
  • Posiada różne właściwości np. typ burgera, rozmiar, dodatki (np. ser, sałata, cebula, pomidor)
  • W tej klasie jest jedna metoda __toString(), która zwraca czytelny opis burgera np. Type: Standard Burger, Size: Medium, Cheese: Yes, Lettuce: Yes, Tomato: Yes, Onion: No

    Taki opis burgera jest czytelny i zawiera wszystkie istotne informacje na temat jego rodzaju, rozmiaru oraz zawartości dodatków.
class Burger {
    public $type;
    public $size;
    public $cheese;
    public $lettuce;
    public $tomato;
    public $onion;

    public function __toString() {
        $burgerStr = "Type: {$this->type}, Size: {$this->size}, Cheese: ";
        $burgerStr .= $this->cheese ? "Yes" : "No";
        $burgerStr .= ", Lettuce: " . ($this->lettuce ? "Yes" : "No");
        $burgerStr .= ", Tomato: " . ($this->tomato ? "Yes" : "No");
        $burgerStr .= ", Onion: " . ($this->onion ? "Yes" : "No");
        return $burgerStr;
    }
}

Interfejs BurgerBuilder (Budowniczy)

  • Interfejs BurgerBuilder definiuje operacje, które muszą być zaimplementowane przez konkretnych budowniczych
  • Zawiera metody do ustawiania typu burgera, rozmiaru oraz dodawania dodatków
interface BurgerBuilder {
    public function setType($type);
    public function setSize($size);
    public function addCheese();
    public function addLettuce();
    public function addTomato();
    public function addOnion();
    public function getBurger(): Burger;
}

StandardBurgerBuilder (Konkretny budowniczy)

  • Klasa StandardBurgerBuilder implementuje interfejs BurgerBuilder (musi zaimplementować wszystkie metody interfejsu)
  • Realizuje kroki do budowy standardowego burgera
  • W konstruktorze tworzony jest nowy obiekt typu Burger (produkt).
  • Posiada metody do ustawienia typu burgera, rozmiaru oraz dodawania składników – metody z interfejsu BurgerBuilder
class StandardBurgerBuilder implements BurgerBuilder {
    private $burger;

    public function __construct() {
        $this->burger = new Burger();

    }

    public function setType($type) {
        $this->burger->type = "Standard Burger";
    }

    public function setSize($size) {
        $this->burger->size = "Medium";
    }

    public function addCheese() {
        $this->burger->cheese = true;
    }

    public function addLettuce() {
        $this->burger->lettuce = true;
    }

    public function addTomato() {
        $this->burger->tomato = true;
    }

    public function addOnion() {
        $this->burger->onion = true;
    }

    public function getBurger(): Burger {
        return $this->burger;
    }
}

VeggieBurgerBuilder (Konkretny budowniczy)

  • Klasa VeggieBurgerBuilder implementuje interfejs BurgerBuilder (musi zaimplementować wszystkie metody interfejsu)
  • Realizuje kroki do budowy wegetariańskiego burgera
  • W konstruktorze tworzony jest nowy obiekt typu Burger (produkt).
  • Posiada metody do ustawienia typu burgera, rozmiaru oraz dodawania składników – metody z interfejsu BurgerBuilder
class VeggieBurgerBuilder implements BurgerBuilder {
    private $burger;

    public function __construct() {
        $this->burger = new Burger();

    }

    public function setType($type) {
        $this->burger->type = "Veggie Burger";
    }

    public function setSize($size) {
        $this->burger->size = "Medium";
    }

    public function addCheese() {
        $this->burger->cheese = true;
    }

    public function addLettuce() {
        $this->burger->lettuce = true;
    }

    public function addTomato() {
        $this->burger->tomato = true;
    }

    public function addOnion() {
        $this->burger->onion = true;
    }

    public function getBurger(): Burger {
        return $this->burger;
    }
}

Klasa BurgerRestaurant (jak dyrektor)

  • klasa działająca jak dyrektor, koordynująca proces konstrukcji burgera
  • używamy klasy BurgerRestaurant, aby zbudować różne typy burgerów, przekazując odpowiedniego budowniczego
  • w konstruktorze constructBurger() definiujemy kroki konstrukcyjne
  • metody getBurger() używamy do uzyskania gotowego burgera
class BurgerRestaurant {
    private $builder;

    public function setBuilder(BurgerBuilder $builder) {
        $this->builder = $builder;
    }

    public function constructBurger() {
        $this->builder->setType("Custom Burger");
        $this->builder->setSize("Large");
        $this->builder->addCheese();
        $this->builder->addLettuce();
        $this->builder->addTomato();
        $this->builder->addOnion();
    }

    public function getBurger(): Burger {
        return $this->builder->getBurger();
    }
}

Wywołanie:

$standardBuilder = new StandardBurgerBuilder();
$veggieBuilder = new VeggieBurgerBuilder();

$restaurant = new BurgerRestaurant();

$restaurant->setBuilder($standardBuilder);
$restaurant->constructBurger();
$burger1 = $restaurant->getBurger();
echo "Standard Burger: " . $burger1 . "\n";

$restaurant->setBuilder($veggieBuilder);
$restaurant->constructBurger();
$burger2 = $restaurant->getBurger();
echo "Veggie Burger: " . $burger2 . "\n";

Tworzymy instancje klas StandardBurgerBuilder oraz VeggieBurgerBuilder. Następnie tworzymy dyrektora (BurgerRestaurant), w którym mamy zdefiniowaną kolejność kroków przygotowywania burgera. Do klasy BurgerRestaurant przekazujemy konkretnego budowniczego, a następnie wywołujemy metodę constructBurger(), aby wykonać kroki tworzenia burgera. Za pomocą metody getBurger() zwracamy przygotowanego burgera.

Podsumowanie

W przypadku budowania burgerów, możemy zauważyć kilka korzyści płynących z zastosowania tego wzorca:

  1. Separacja procesu konstrukcji od samego obiektu – Wzorzec Budowniczy pozwala na oddzielenie procesu tworzenia burgera od samego burgera. Dzięki temu możemy elastycznie zarządzać procesem tworzenia, modyfikując lub rozszerzając kroki budowy bez wpływu na same obiekty burgerów.
  2. Elastyczność w konfiguracji – Poprzez korzystanie z budowniczych możemy tworzyć różne warianty burgerów, modyfikując tylko niektóre kroki konstrukcyjne. Na przykład, możemy mieć różne rodzaje sera, dodatki warzywne lub rozmiary burgerów, które można łatwo dostosować poprzez wybór odpowiedniego budowniczego i manipulację jego krokami.
  3. Możliwość rozszerzania – Wzorzec Budowniczy umożliwia łatwe dodawanie nowych rodzajów burgerów lub modyfikowanie istniejących kroków konstrukcyjnych bez konieczności modyfikowania samej klasy produktu czy innych komponentów.

0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *