Builder Pattern : Crafting complex objects piece by piece
What is Builder Pattern?
The Builder Pattern is a way to build a complex object step by step. This pattern is particularly useful when you need to create an object with many different configurations.
Decoding the Jargon: A Simple Analogy
If you’re anything like me, you find that breaking down technical jargon into simpler terms and analogies makes learning and remembering concepts much easier.
Let’s picture this: You’re at Subway, and you’re about to craft your masterpiece of a sandwich. You don’t just stroll in and say, “One sandwich, please!” and leave your sandwich fate to the sandwich gods. Oh no, you build it meticulously! You select your bread with the same care as choosing your socks (hopefully!). You pick your fillings the way you pick a Netflix series, totally based on your mood! 🧦📺
It’s like assembling a tasty piece of art, one ingredient at a time! 🥪
This is essentially the Builder Pattern in a nutshell, constructing objects piece by piece, allowing you the autonomy to create something precisely to your liking, whether it’s a sandwich or a piece of software! 🛠️
I hope this analogy helps in making the Builder Pattern a bit more relatable and easier to grasp!
A Practical Illustration
Imagine you’ve landed a role at a leading company in the property technology sector, think along the lines of Airbnb, Booking.com, or Housemates. On your first day, you're handed a piece of code and told, "This is how we've been managing property listings."
// Property for Downing Students
$downingStudentProperty = new stdClass();
$downingStudentProperty->type = 'Apartment';
$downingStudentProperty->owner='Downing Students';
$downingStudentProperty->rooms = 3;
$downingStudentProperty->hasGarage = true;
$downingStudentProperty->hasGarden = false;
$downingStudentProperty->setLocation('Manchester');
// Property for Homes for Students
$homesForStudentProperty = new stdClass();
$homesForStudentProperty->type = 'Apartment';
$homesForStudentProperty->owner='Downing Students';
$homesForStudentProperty->rooms = 3;
$homesForStudentProperty->hasGarage = true;
$homesForStudentProperty->hasGarden = false;
$homesForStudentProperty->setLocation('Manchester');
You quickly realize that it's written in procedural PHP. While it gets the job done, you can't help but think there might be a more structured and scalable approach, especially as the platform grows and evolves.
Embracing the Builder Pattern
For scenarios like the one we’ve discussed, where the incremental construction of the property object is key, the Builder Pattern truly shines. It allows us to gracefully add functionalities step by step, ensuring a seamless and coherent development of our object. Here is how we can refactor the above code using Builder Pattern
class PropertyBuilder
{
public function __construct(
protected Property $property
) {
}
public function setType($type)
{
$this->property->type = $type;
return $this;
}
public function setOwner($type)
{
$this->property->owner = $type;
return $this;
}
public function setRooms($rooms)
{
$this->property->rooms = $rooms;
return $this;
}
public function setHasGarage($hasGarage)
{
$this->property->hasGarage = $hasGarage;
return $this;
}
public function setHasGarden($hasGarden)
{
$this->property->hasGarden = $hasGarden;
return $this;
}
public function setLocation($location)
{
$this->property->location = $location;
return $this;
}
public function build()
{
return $this->property;
}
}
// property could be coming from any where e.g. eloquent model
$property = new Property();
$downingStudentProperty = (new PropertyBuilder($property))
->setType('Apartment')
->setOwner('Downing Students')
->setRooms(3)
->setHasGarage(true)
->setHasGarden(false)
->setLocation('Manchester')
->build();
This approach is notably more streamlined compared to the previous procedural code. We are using a much simplified example, but imagine if each step involved, needed additional computations or API calls.
As a user of this class, is it essential to grasp the intricate details of every step involved in creating a property? The likely answer is NO. We’re altering behavior and incrementally adding features to the Property object, and this step-by-step enhancement is the essence of the Builder Pattern.
Meet the Director
Our code is already more streamlined and refined, but let’s take it one step further. When dealing with a complex object that requires multiple steps for its creation, it would be highly cool if we could delegate the responsibility of remembering and executing each step, allowing us to simply supply the required arguments.
This is where the Director class steps in, acting as the maestro of the Builder Pattern. It oversees the execution of each construction step in the correct sequence, ensuring the flawless creation of the product, in this scenario, a Property. The Director focuses on orchestrating the construction process while leaving the intricacies of each step to the Builder.
class PropertyDirector
{
public function __construct(
protected BuilderInterface $builder
) {
}
public function buildProperty($type, $owner, $rooms, $hasGarage, $hasGarden, $location)
{
return $this->builder
->setType($type)
->setRooms($rooms)
->setHasGarage($hasGarage)
->setHasGarden($hasGarden)
->setLocation($location)
->build();
}
}
$property = new Property();
$director = new PropertyDirector(
new PropertyBuilder($property)
);
$property = $director->buildProperty(
type: 'Apartment',
owner: 'Downing Students',
rooms: 3,
hasGarage: true,
hasGarden: false,
location: 'Manchester'
);
To make our code even more flexible, we can introduce contracts. Instead of the Director
accepting a concrete class, it can accept a contract that stipulates the inclusion of a build
method. This approach allows us to interchange the PropertyBuilder
seamlessly at runtime, offering a higher degree of flexibility and adaptability in our application. Below is a quick implementation demonstrating this approach:
interface BuilderInterface
{
public function build();
}
class PropertyBuilder implements BuilderInterface
{
public function __construct(
protected Property $property
) {
}
// ... other setter methods ...
public function build()
{
return $this->property;
}
}
class PropertyDirector
{
public function __construct(
protected BuilderInterface $builder
) {
}
public function buildProperty($type, $owner, $rooms, $hasGarage, $hasGarden, $location)
{
// steps required to build a property
}
}
$property = new Property();
$director = new PropertyDirector(new PropertyBuilder($property));
$property = $director->buildProperty(
type: 'Apartment',
owner: 'Downing Students',
rooms: 3,
hasGarage: true,
hasGarden: false,
location: 'Manchester'
);
A Prime Example in Practice
You might not have realized it, but if you’ve ever used Laravel's Query Builder, you’ve already had a hands-on experience with the Builder Pattern!
Laravel's Query Builder serves as a quintessential illustration of the Builder Pattern at work. It facilitates the construction of a database query in a step-by-step manner, offering a readable and user-friendly interface to craft queries programmatically, making it a breeze to interact with databases.
Here’s a simple example of Laravel’s Query Builder:
$query = DB::table('properties')
->select('type', 'rooms', 'location')
->where('hasGarage', true)
->where('hasGarden', false)
->get();
Some use cases
You might consider using this pattern in your day to day web development flow. Some of the use cases where i successfully used that pattern are as follow
- Form Generation
When dealing with dynamic form generation where forms can have different fields, validations, and styles based on different conditions or user types, the Builder Pattern can be used to construct the form object step by step.
$form = (new FormBuilder())
->setAction('/submit')
->setMethod('POST')
->addField('text', 'username')
->addField('password', 'password')
->addValidation('username', 'required')
->addValidation('password', 'required')
->build();
- API Response Construction
When building responses for a RESTful API, different endpoints might require different response structures. The Builder Pattern can help in constructing these responses.
$response = (new ApiResponseBuilder())
->setStatus(200)
->setData($data)
->addHeader('Content-Type', 'application/json')
->build();
- HTML Element Creation
When creating complex HTML elements that can have various attributes, styles, and children, the Builder Pattern can be used to construct these elements.
$element = (new HtmlElementBuilder())
->setTag('div')
->addClass('container')
->addAttribute('data-id', '123')
->addChild($childElement)
->build();
Comparison to Other Creational Patterns
Often, the Builder
Pattern is confused with the Factory
and Prototype
patterns as they all deal with object creation. However, the Builder Pattern is distinct in its approach to constructing complex objects step by step. In contrast, the Factory
Pattern is about creating an instance of a class, and the Prototype
Pattern is about cloning an existing object.
More about them in some other post.
Conclusion
The Builder Pattern offers a structured method for constructing complex objects piece by piece in PHP. It stands out for its clarity, flexibility, and maintainability, especially when compared to other creational patterns. By adopting such patterns, you can ensure a cleaner and more efficient codebase, adaptable to the evolving needs of the project.