Wzorce projektowe, cz. 9 Builder

Budowniczy jest wzorcem, gdzie proces tworzenia obiektu podzielony jest na kilka mniejszych etapów, a każdy z nich może być implementowany na wiele sposobów. Dzięki takiemu rozwiązaniu możliwe jest tworzenie różnych reprezentacji obiektów w tym samym procesie konstrukcyjnym.


Diagram klas wzorca Builder

Standardowo wzorzec składa się z dwóch podstawowych elementów. Pierwszy z nich oznaczony jest jako Builder – jego celem jest dostarczenie interfejsu do tworzenia obiektów nazywanych produktami (product). Drugim elementem jest obiekt oznaczony jako ConcreteBuilder, a jego celem jest tworzenie konkretnych reprezentacji produktów przy pomocy zaimplementowanego interfejsu Builder. W ConcreteBuilder zawarte są procedury odpowiedzialne za konstrukcje i inicjalizację obiektu. Strukturę wzorca uzupełnia obiekt Director (zwany także czasem kierownikiem, nadzorcą – tak jak na budowie ;)), który zleca konstrukcję produktów poprzez obiekt Builder dbając o to, aby proces budowy przebiegał w odpowiedniej kolejności.


Diagram sekwencji wzorca Builder

Przykładowa implementacja

<?php

class Product {

    private $part1;
    private $part2;

    public function setPart1($part1) {
        $this->part1 = $part1;
    }
    public function getPart1() {
        return $this->part1;
    }
    public function setPart2($part2) {
        $this->part2 = $part2;
    }
    public function getPart2() {
        return $this->part2;
    }

}

interface Builder {
    public function buildPart1();
    public function buildPart2();
}

class ConcreteBuilder implements Builder {
    private $product;

    public function __construct() {
        $this->product = new Product();
    }
    public function buildPart1() {
        $this->product->setPart1("1. faza budowy");
    }
    public function buildPart2() {
        $this->product->setPart2("2. faza budowy");
    }
    public function getProduct() {
        return $this->product;
    }

}

class Director{
    private $builder;

    public function __construct() {
        $this->builder = new ConcreteBuilder();
    }
    public function construct() {
        $this->builder->buildPart1();
        $this->builder->buildPart2();
    }
    public function getResult() {
        return $this->builder->getProduct();
    }
}

// testy
$director = new Director();
$director->construct();
$product = $director->getResult();
echo $product->getPart1(); // wyswetli "1. faza budowy"
echo $product->getPart2(); // wyswieli "2. faza budowy"

?>

Przykład z życia wzięty

Tworząc serwis udostępniający filmy wideo chcemy mieć możliwość wyboru technologii do generowania odtwarzacza wideo. Możemy użyć do tego budowniczych.

<?php
class Player {
    private $playerName;

    public function setplayerName($playerName) {
        $this->playerName = $playerName;
    }
    public function render() {
        return $this->playerName;
    }
}

interface Builder {
    public function buildPlayer();
    public function getPlayer();
}

class FlashBuilder implements Builder {
    private $player;

    public function __construct() {
        $this->player = new Player();
    }
    public function buildPlayer() {
        $this->player->setplayerName("Player w Flash");
    }
    public function getPlayer() {
        return $this->player;
    }
}

class HTMLBuilder implements Builder {
    private $player;

    public function __construct() {
        $this->player = new Player();
    }
    public function buildPlayer() {
        $this->player->setplayerName("Player w HTML5");
    }
    public function getPlayer() {
        return $this->player;
    }
}

class Director{
    private $builder;

    public function __construct(Builder $builder) {
        $this->builder = $builder;
    }
    public function construct() {
        $this->builder->buildPlayer();
    }
    public function getResult() {
        return $this->builder->getPlayer();
    }
}

// testy
$html = new HTMLBuilder();
$flash = new FlashBuilder();
$director = new Director($flash);
$director->construct();
$player = $director->getResult();
echo $player->render(); // wyswietli "player w flash"

$director2 = new Director($html);
$director2->construct();
$player2 = $director2->getResult();
echo $player2->render(); // wyswietli "player w HTML5"

?>

Dwaj budowniczy FlashBuilder i HTMLBuilder mają za zadanie stworzyć obiekt Video z uwzględnieniem wykorzystanej technologii. Z kolei Director „pilnuje” poprawnej kolejności wywoływania metod. Dzięki wykorzystaniu Dependency Injection możemy łatwo dodawać kolejnych budowniczych.

Zalety i wady

Zalety:

  • Duża możliwość zróżnicowania wewnętrznych struktur klas.
  • Duża skalowalność (dodawanie nowych reprezentacji obiektów jest uproszczone).
  • Większa możliwość kontrolowania tego, w jaki sposób tworzony jest obiekt (proces konstrukcyjny jest niezależny od elementów, z których składa się tworzony obiekt.

Wady:

  • Duża liczba obiektów reprezentujących konkretne produkty.
  • Nieumiejętne używanie wzorca może spowodować nieczytelność kodu (jeden produkt może być tworzony przez zbyt wielu budowniczych).

Zastosowanie

Wzorzec budowniczego stosowany jest do oddzielenia sposobu tworzenia obiektów od tego jak te obiekty mają wyglądać. Przykładem jest oprogramowanie konwertujące tekst z jednego formatu na drugi. Algorytm odczytujący i interpretujący dane wejściowe jest oddzielony od algorytmu tworzącego dane wyjściowe. Dzięki takiemu rozwiązaniu możliwe jest zastosowanie jednego obiektu odczytującego dane wejściowe oraz wielu obiektów konwertujących odczytane dane do różnych formatów (ASCII, HTML, RTF, itp.), co zwiększa skalowalność rozwiązania.

Innym zastosowaniem może być stworzenie narzędzia, które będzie miało różną implementację zależnie od użytej technologii – tak jak w omawianym przykładzie odtwarzacz wideo.

Powiązane tematy

Print Friendly, PDF & Email