Posted by Keyvan Akbary on Sep 25, 2014

Builder pattern

The builder pattern fits into the category of creational patterns. This means that its use is intended for building up objects. The original idea discussed in the classic Gang of Four book focuses on decoupling the construction code from the representation code.

The internal aspects that are involved in the construction of the object are not exposed through the Builder public API. The client shouldn’t know about the process required to build a complex object. Using this pattern also makes it easy to manage methods with many parameters.

If we have an object or a product with complex construction, like a delicious and abstract burger

class Burger {
    private $patty;
    private $toppings = [];
    private $bun;
 
    public function setBun($bun) {
        $this->bun = $bun;
    }
 
    public function setPatty($patty) {
        $this->patty = $patty;
    }
 
    public function addToppings(array $toppings) {
        $this->toppings = $toppings;
    }
}

And we need to build it following different recipes; we could create an abstract Builder that specialises the recipe details through concrete implementations by using the template method pattern

abstract class BurgerBuilder {
    protected $burger;
 
    public function createBurger() {
        $this->burger = new Burger();
    }
 
    public function getBurger() {
        return $this->burger;
    }
 
    abstract public function prepareBun();
    abstract public function cookPatty();
    abstract public function addToppings();
}

Like a veggie burger

class VeggieBurgerBuilder extends BurgerBuilder {
    public function prepareBun() {
        $this->burger->setBun('brioche'); 
    }
 
    public function cookPatty() {
        $this->burger->setPatty('halloumi'); 
    }
 
    public function addToppings() {
        $this->burger->addToppings(['cauliflower', 'tomato', 'onion', 'cheese']); 
    }
}

Or an american one…

class AmericanBurgerBuilder extends BurgerBuilder {
    public function prepareBun() {
        $this->burger->setBun('sesame'); 
    }
 
    public function cookPatty() {
        $this->burger->setPatty('beef'); 
    }
 
    public function addToppings() {
        $this->burger->addToppings(['tomato', 'cheese', 'onion', 'pickles', 'bacon']); 
    }
}

The director, the chef, controls and manages accurately the product creation process

class BurgerChef {
    public function makeBurger(BurgerBuilder $builder) {
        $builder->createBurger();
        $builder->prepareBun();
        $builder->cookPatty();
        $builder->addToppings();
 
        return $builder->getBurger();
    }
}

The client remains simple and free of the internal build requirements

$chef = new BurgerChef();
$vegieBurger = $chef->makeBurger(new VeggieBurgerBuilder());
$americanBurger = $chef->makeBurger(new AmericanBurgerBuilder());

Telescopic Constructor

A recurring problem in languages with method overloading like Java, C# or C++ is the telescopic constructor. In PHP we are unable to overload methods, however, use of factory methods in combination with a private constructor results in the same situation.

class User {
    private $username;
    private $password;
    private $email;
    private $name;

    private function __construct($username, $password, $email = '', $name = '') {
        $this->username = $username;
        $this->password = $password;
        $this->email = $email;
        $this->name = $name;
    }

    public static function create($username, $password) {
        return new self($username, $password);
    }

    public static function createWithEmail($username, $password, $email) {
        return new self($username, $password, $email);
    }

    public static function createWithName($username, $password, $name) {
        return new self($username, $password, '', $name);
    }

    public static function createWithEmailAndName($username, $password, $email, $name) {
        return new self($username, $password, $email, $name);
    }
}

Adding constructor arguments increases the requirement for factory methods exponentially. By delegating to a Builder the User construction and using a fancy fluent interface we decrease the complexity of the system. The trade-off is that we need to expose the product constructor in order to make it visible to the Builder.

class UserBuilder {
    private $username;
    private $password;
    private $email = '';
    private $name = '';

    private function __construct($username, $password) {
        $this->username = $username;
        $this->password = $password;
    }

    private static function aUser($username, $password) {
        return new self($username, $password);
    }

    public function withName($name) {
        $this->name = $name;

        return $this;
    }

    public function withEmail($email) {
        $this->email = $email;

        return $this;
    }

    public function build() {
        return new User($this->username, $this->password, $this->email, $this->name);
    }
}

Creating a User without a name and email is as easy as

$user = UserBuilder::aUser('keyvan', 'pass')->build();

In a similiar fashion, we are also able to expand on this example and use the optional parameters

$user = UserBuilder::aUser('keyvan', 'pass')
    ->withName('Keyvan Akbary')
    ->withEmail('keyvan@example.com')
    ->build();

Jobs at MyBuilder

We need an experienced software engineer who loves their craft and wants to share their hard-earned knowledge.

View vacancies
comments powered by Disqus