Introduction to Fundamental Design Patterns - Part 2
Learn to manage complicated object creation with the Builder pattern in applications
Introduction
In the first part of this series, we explored fundamental design patterns and their importance in creating maintainable and scalable code. This time, we'll delve into the Builder pattern, a creational pattern that simplifies the construction of complex objects.
Builder pattern
The Builder pattern is a creational design pattern that separates the construction of a complex object from its representation. This allows the same construction process to create different representations.
Real-world - scenario
Imagine you're ordering a customized burger. You can choose different buns, patties, toppings, and sauces. The Builder pattern is like having a "burger builder" who takes your order step by step:
"Bun?" - You choose a sesame bun.
"Patty?" - You choose a double beef patty.
"Toppings?" - You choose lettuce, tomato, and onion.
"Sauce?" - You choose BBQ sauce.
Imagine you're ordering a customized burger. You can choose different buns, patties, toppings, and sauces. The Builder pattern is like having a "burger builder" who takes your order step by step.
How your implementation will be without a builder design pattern.
<?php
class Burger {
private string $bun;
private string $patty;
private array $toppings;
private string $sauce;
public function __construct(string $bun, string $patty, array $toppings = [], string $sauce = "") {
$this->bun = $bun;
$this->patty = $patty;
$this->toppings = $toppings;
$this->sauce = $sauce;
}
public function __toString(): string {
$toppingString = implode(", ", $this->toppings);
return "Burger with {$this->bun} bun, {$this->patty} patty, toppings: {$toppingString}, and {$this->sauce} sauce.";
}
}
// Creating burgers (becomes cumbersome with more options)
$burger1 = new Burger("Sesame", "Beef");
$burger2 = new Burger("Whole Wheat", "Chicken", ["Lettuce", "Tomato"]);
$burger3 = new Burger("Brioche", "Double Beef", ["Lettuce", "Cheese", "Bacon"], "BBQ");
echo $burger1 . "\n";
echo $burger2 . "\n";
echo $burger3 . "\n";
?>
Problems with this approach:
Readability: Creating complex burgers becomes less readable due to long constructor calls.
Maintainability: Adding or removing options requires modifying the
Burger
class and potentially all the constructors.
Builder Pattern in action
<?php
class Burger
{
private string $bun;
private string $patty;
private array $toppings = [];
private string $sauce = "";
public function __toString(): string
{
$toppingString = implode(", ", $this->toppings);
return "Burger with {$this->bun} bun, {$this->patty} patty, toppings: {$toppingString}, and {$this->sauce} sauce.";
}
public function setBun(string $bun): void
{
$this->bun = $bun;
}
public function setPatty(string $patty): void
{
$this->patty = $patty;
}
public function addTopping(string $topping): void
{
$this->toppings[] = $topping;
}
public function setSauce(string $sauce): void
{
$this->sauce = $sauce;
}
}
interface BurgerBuilderInterface
{
public function setBun(string $bun);
public function setPatty(string $patty);
public function addTopping(string $topping);
public function setSauce(string $sauce);
public function getBurger(): Burger;
}
class BurgerBuilder implements BurgerBuilderInterface
{
private Burger $burger;
public function __construct()
{
$this->burger = new Burger();
}
public function setBun(string $bun): BurgerBuilder
{
$this->burger->setBun($bun);
return $this;
}
public function setPatty(string $patty): BurgerBuilder
{
$this->burger->setPatty($patty);
return $this;
}
public function addTopping(string $topping): BurgerBuilder
{
$this->burger->addTopping($topping);
return $this;
}
public function setSauce(string $sauce): BurgerBuilder
{
$this->burger->setSauce($sauce);
return $this;
}
public function getBurger(): Burger
{
return $this->burger;
}
}
// Creating burgers (much more readable and flexible)
$burgerBuilder = new BurgerBuilder();
$burger1 = $burgerBuilder->setBun("Sesame")
->setPatty("Beef")
->getBurger();
$burger2 = (new BurgerBuilder())->setBun("Whole Wheat")
->setPatty("Chicken")
->addTopping("Lettuce")
->addTopping("Tomato")
->getBurger();
$burger3 = (new BurgerBuilder())
->setBun("Brioche")
->setPatty("Double Beef")
->addTopping("Lettuce")
->addTopping("Cheese")
->addTopping("Bacon")
->setSauce("BBQ")
->getBurger();
echo $burger1 . "\n";
echo $burger2 . "\n";
echo $burger3 . "\n";
?>
The construction process is now more readable and expressive. You can easily see which ingredients are being added.
Adding new ingredients doesn't require modifying the Burger
class or creating new constructors. You just add a new method to the BurgerBuilder
.
Improved Maintainability Changes to the construction process are isolated to the BurgerBuilder
class, making the code easier to maintain and extend.
This example demonstrates how the Builder pattern improves code readability and maintainability, especially when dealing with complex objects that have many optional components.
Conclusion
The Builder pattern offers an elegant solution by separating the construction process from the object's representation. This allows you to create different variations of an object using the same construction process. This concludes the second part of our design pattern series. In the next article, we will explore another powerful creational pattern.
๐ Like & Share this article with your engineering friends to spread the knowledge! Your support motivates me to create more content like this and help developers grow. Keep learning, keep coding, and stay tuned for Part 3! ๐