Builder pattern

When we reviewed the Factory design patterns, we saw how they were useful for enabling polymorphism. The crucial differentiation between Factory patterns and the Builder pattern is that the Builder pattern solely has the aim of resolving one anti-pattern and does not seek to perform polymorphism. The anti-pattern in question is the Telescoping Constructor.

The Telescoping Constructor problem is essentially where the count of arguments a constructor contains grows to an extent where it becomes impractical to use or even impractical to know which order the arguments go in.

Let's suppose we have a Pizza class as follows, it essentially contains a constructor and a show function which details the size and toppings of the pizza. The class looks like this:

<?php 
 
class Pizza 
{ 
 
  private $size; 
  private $cheese; 
  private $pepperoni; 
  private $bacon; 
 
  public function __construct($size, $cheese, $pepperoni, $bacon) 
  { 
    $this->size = $size; 
    $this->cheese = $cheese; 
    $this->pepperoni = $pepperoni; 
    $this->bacon = $bacon; 
  } 
 
  public function show() 
  { 
    $recipe = $this->size . " inch pizza with the following toppings: "; 
    $recipe .= $this->cheese ? "cheese, " : ""; 
    $recipe .= $this->pepperoni ? "pepperoni, " : ""; 
    $recipe .= $this->bacon ? "bacon, " : ""; 
 
    return $recipe; 
  } 
 
} 

Notice how many parameters the constructor contains, it literally contains the size and then every single topping. We can do better than this. In fact, let's aim to construct the pizza by adding all our parameters to a builder object that we can then use to create the pizza. This is what we're aiming for:

$pizzaRecipe = (new PizzaBuilder(9)) 
  ->cheese(true) 
  ->pepperoni(true) 
  ->bacon(true) 
  ->build(); 
 
$order = new Pizza($pizzaRecipe); 

This isn't too hard to do; in fact you might even find it to be one of the easier design patterns we learn here. Let's first start by making a builder for our pizza, let's name this class PizzaBuilder:

<?php 
 
class PizzaBuilder 
{ 
  public $size; 
  public $cheese; 
  public $pepperoni; 
  public $bacon; 
 
  public function __construct(int $size) 
  { 
    $this->size = $size; 
  } 
 
  public function cheese(bool $present): PizzaBuilder 
  { 
    $this->cheese = $present; 
    return $this; 
  } 
 
  public function pepperoni(bool $present): PizzaBuilder 
  { 
    $this->pepperoni = $present; 
    return $this; 
  } 
 
  public function bacon(bool $present): PizzaBuilder 
  { 
    $this->bacon = $present; 
    return $this; 
  } 
 
  public function build() 
  { 
    return $this; 
  } 
} 

This class isn't too hard to understand, we have a constructor that sets the size, and for each additional topping we want to add we can then just call the relevant topping method with the parameter set to true or false accordingly. If the topping method isn't called, the topping in question isn't set as a parameter.

Finally, we have a build method, which can be called to run any last minute logic to organize data before it's sent into the constructor of the Pizza class. This said, I often don't like to do this, as this can be considered sequential coupling if methods need to be in a particular order and this would intrinsically defeat one purpose of us making a builder to do tasks like this.

For this reason, every topping method also returns the object that they are creating, allowing the output of any function to directly be injected into whatever class we want to use it to construct.

Next, let's adapt our Pizza class to utilize this builder:

<?php 
 
class Pizza 
{ 
 
  private $size; 
  private $cheese; 
  private $pepperoni; 
  private $bacon; 
 
  public function __construct(PizzaBuilder $builder) 
  { 
    $this->size = $builder->size; 
    $this->cheese = $builder->cheese; 
    $this->pepperoni = $builder->pepperoni; 
    $this->bacon = $builder->bacon; 
  } 
 
  public function show() 
  { 
    $recipe = $this->size . " inch pizza with the following toppings: "; 
    $recipe .= $this->cheese ? "cheese, " : ""; 
    $recipe .= $this->pepperoni ? "pepperoni, " : ""; 
    $recipe .= $this->bacon ? "bacon, " : ""; 
 
    return $recipe; 
  } 
 
} 

It's quite straightforward for a constructor; we just access the public properties in the builder as and when they're needed.

Note that we can add additional validation of the data provided from the builder in the constructor here, though you can also add validation when you're setting the methods in the builder, depending on the type of logic required.

Now we can put all this together in our index.php file:

<?php 
 
require_once('Pizza.php'); 
require_once('PizzaBuilder.php'); 
 
$pizzaRecipe = (new PizzaBuilder(9)) 
  ->cheese(true) 
  ->pepperoni(true) 
  ->bacon(true) 
  ->build(); 
 
 
$order = new Pizza($pizzaRecipe); 
echo $order->show(); 

The output we should get looks something like this:

Builder pattern

The Builder design pattern is incredibly easy to adopt but can save a lot of headaches when constructing objects.

The disadvantage of this method is the need for a separate Builder for every single class; this is the cost for such control over the object construction process.

Above this, the Builder design pattern allows you to vary the constructor variables and also provides for good encapsulation of the code that constructs an object itself. Like all design patterns, it's down to you to decide where it's most appropriate to use each one in your code.

Traditionally, key-value arrays were often used in substitution of Builder classes. Builder classes however, give you far more control over the construction process.

There's one other thing I should mention; here, we just referenced the methods using our index.php method; often, the methods we run there are placed in a class that can be referred to as the Director class.

Above this, you can also consider applying an interface to implement in your Builder if your Builder is going to have a lot of logic in.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.126.56