Consider the complexity of the device you are looking at right now; there's a lot of moving parts! To represent the process of assembling your device in code could quickly become a difficult task if you start to account for different screen sizes and processors, to name a few. When faced with the problem of needing to create an object with lots of interchangeable parts, the builder pattern offers a balance to flexibility and complexity.
Assumptions
- Basic OOP knowledge
- Knowledge of basic <a href="https://www.devmaking.com/learn/design-patterns/design-pattern-modeling/" target="_blank" style="color:inherit;">Modeling Notation</a>
- Knowing the <a href="https://www.devmaking.com/learn/design-patterns/factory-pattern/" target="_blank" style="color:inherit;">Factory Pattern</a> is a plus!
What is the Builder Design Pattern?
The builder pattern is an object creational design pattern that encapsulates the construction of a complex object, keeping it separate from it's representation. The builder pattern is like an assembly line; it makes parts of an object, one at a time. When all the pieces are put together, the builder is able to return a finished product!
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/BuilderPattern_01.png" alt="builder high level UML diagram" style="max-width:95%;"> </div>
The builder pattern is also flexible; different types of similar objects can be constructed using the same process. Additionally, it creates a standard flow for object creation, so mistakes are less likely.
Conceptualization
Imagine you wanted to create your own contact list database of all your friends with their names, contact details, and maybe some interesting facts. If you were to create a class to represent a Contact
, it might look like this:
/*pseudo-code*/
class Contact {
// Member variables.
String firstName;
String lastName;
int age;
String phoneNumber;
String[] facts;
// Class constructor
constructor(String firstName, String lastName,
int age, String phoneNumber, String[] facts) {
this.firstName = firstName;
/* And so on...*/
this.facts = facts;
}
}
> So many variables !
It's safe to say that this object is complex. If a developer was to instantiate the class, it might look like this:
Contact contactEntry = new Contact("Bruce",
"Wayne",
28,
"555-555-1234",
["Billionaire",
"Brooding",
"Want's to help the city",
"Has a strange knack for disappearing"]);
Not only is this ugly, a new set of eyes would have trouble interpreting the code, and it creates a lot of room for mistakes between missing variables and mixing them up!
Instead, we can create a builder, which we'll call ContactBuilder
, that will facilitate the creation process of future contacts:
class ContactBuilder {
// 1.
String firstName;
String lastName;
int age;
String phoneNumber;
String[] facts;
// 2.
ContactBuilder setFirstName(String first) {
this.firstName = first;
return this;
}
ContactBuilder setLastName(String last) {
this.lastName = last;
return this;
}
ContactBuilder setAge(int age) {
this.age = age;
return this;
}
ContactBuilder setPhoneNumber(String number) {
this.phoneNumber = number;
return this;
}
ContactBuilder addFact(String fact) {
this.facts.append(fact);
return this;
}
// 3.
Contact buildContact() {
return new Contact(firstName, lastName, age, phoneNumber, facts);
}
}
There's quite a bit happening here, so let's take it piece by piece:
- In most implementations of builders, you'll have a copy of the member variables of the complex object. Now, the builder is able to store and change the variables without the contact needing to expose any of it's variables or be updated.
- In the second part of a builder, we define the variables that can be set in the class. This is where the actual "building" takes place. Note the return type of these functions! (more on this shortly)
- Finally, when the contact is needed, we can call the function
buildContact
, which will return a new contact object to the user with all of the information already in it!
Putting it Together
Now that we've seen the builder implemented, here's what the overall design looks like:
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/BuilderPattern_02.png" alt="Contact builder UML diagram." style="max-width:95%;"> </div>
> Note that we didn't implement the builder interface here as it wasn't necessary, however sometimes you will want an abstract builder to implement concrete builders from!
Before and After:
/* Before Builder: */
Contact contactEntry = new Contact("Barry",
"Allen",
26,
"444-555-1234",
["Scientist",
"Detective",
"Seems to be in many places at once"]);
/* After Implementing Builder: */
ContactBuilder builder = newContactBuilder();
builder.setFirstName("Barry")
.setLastName("Allen")
.setAge(26)
.setPhoneNumber("444-555-1234")
.addFact("Scientist")
.addFact("Detective")
.addFact("Seems to be in many places at once");
// get the output of the builder:
Contact builtContactEntry = builder.buildContact();
Now it is much easier to recognize what each variable means when using the builder pattern!
Chaining Functions
If you look back at the ContactBuilder
implementation, you'll notice that each of the functions return the builder class itself. This allows us to "chain" our builder functions on top of each other without needing to write builder.set..()
over and over again.
> This isn't a requirement of the builder pattern, however it can make for much better readability, which is one of the end goals of the builder pattern!
Builder Pattern or Factory Pattern?
Sometimes it can be confusing whether the Builder Pattern or Factory pattern will emerge from your code, which is why builder and factory have this important distinction:
- The builder pattern composes a complex object i.e., creating a contact entry.
- The factory pattern produces a family of objects i.e., creating pre-assembled UI elements.
Recommended Resources
- <a target="_blank" href="https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=devmaking-20&linkId=bc32087110669f75d93b216df79816f0" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Head First Design Patterns: A Brain-Friendly Guide</a> : An excellent primer for learning design patterns in a pragmatic way.
- <a target="_blank" href="https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=devmaking-20&linkId=ae25d94c4ea49870eb3115e0e4d2de90" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Design Patterns: Elements of Reusable Object-Oriented Software</a> : The reference guide made famous by Gang of Four, still widely used today.
- <a href="https://www.draw.io" target="_blank" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Draw.io</a>: A free, open-source tool for designing diagrams with built-in support for UML diagrams to make your own!