Builder
Quick Information/Overview
Pattern Type | Creational |
Applicable Language/Framework | Agnostic OOP |
Pattern Source | Gang of Four |
Difficulty | Relatively easy. |
Up Front Definitions
Here are terms that I’ll be using in this explanation and what they mean:
- Composite: This is another design pattern, but I’m using it here to mean an object that is composed of other objects.
The Problem
For this design pattern, I’ll use the example of modeling computers. A computer is a classic example of a composite object. That is, a computer object isn’t just a collection of simple literals, but is actually composed of other objects in your domain (most likely anyway, and it will be in the example).
So, let’s say that you get an assignment to model computer composition and pricing, perhaps for a website for a store that builds custom rigs. For a proof of concept, let’s say that you very much oversimplify things and decide to create an implementation where a computer has memory, a CPU, and a motherboard.
Now, since we’re modeling price rather than actual operation, it’s important to keep in mind that the abstraction for actual computer construction and sales is limited to “what will work with what” rather than modeling the actual interaction. So, if CPU X doesn’t work with motherboard Y, you don’t want to sell that combination. But, worrying about the details of how CPU x works with motherboard Y is beyond the scope of this application. Another thing to consider here is that all components have a price, and the assembled machine will have a price that equals the sum of its components, perhaps with a cost of assembly added as well.
Now, let’s also assume that there is a dependency relationship in ordering how machines are built. That is, you have an external requirement of some sort that the motherboard must be chosen first, followed by the CPU, and then by the memory. So, you create objects for your components and a computer object, and it looks something like the following (remember, this is an example and an example of a POC at that – let’s not get too immersed in the details)
public abstract class Cpu
{
public int Cores { get; set; }
public int ProcessorSpeed { get; set; }
public abstract double Price { get; }
public virtual bool IsCompatibleWith(Memory memory)
{
return true;
}
}
public class Intel : Cpu
{
public override double Price { get { return 300.00; } }
}
public class Athalon : Cpu
{
public override double Price { get { return 140.00; } }
}
public abstract class MotherBoard
{
public int PciSlots { get; set; }
public int IdeSlots { get; set; }
public abstract double Price { get; }
public virtual bool IsCompatibleWith(Cpu cpu)
{
return true;
}
public virtual bool IsCompatibleWith(Memory memory)
{
return true;
}
}
public class Acer : MotherBoard
{
public override double Price { get { return 200.00; } }
}
public class IntelMobo : MotherBoard
{
public override double Price { get { return 500.00; } }
}
public abstract class Memory
{
public int Slots { get; set; }
public int CapacityPerSlot { get; set; }
public abstract double Price { get; }
}
public class Crucial : Memory
{
public override double Price { get { return 100.00; }
}
}
public class Kingston : Memory
{
public override double Price { get { return 200.00; } }
}
And, finally, the computer:
public class Computer
{
private MotherBoard _motherboard;
private Cpu _cpu;
private Memory _memory;
public double GetPrice()
{
return _memory.Price + _cpu.Price + _motherboard.Price; //Fails if anyone is null
}
public bool AddMotherboard(MotherBoard motherboard)
{
_motherboard = motherboard;
return true;
}
public bool AddCpu(Cpu cpu)
{
if (_motherboard == null || !_motherboard.IsCompatibleWith(cpu))
{
return false;
}
_cpu = cpu;
return true;
}
public bool AddMemory(Memory memory)
{
if (_motherboard == null || !_motherboard.IsCompatibleWith(memory) ||
_cpu == null || !_cpu.IsCompatibleWith(memory))
{
return false;
}
_memory = memory;
return true;
}
}
And, life is good. You have a computer class that can have components added to it in the proper order, with each add returning false if you’ve made an oops. The compatibility standards are a little permissive at the moment, but, remember, POC.
So, the next thing that you’d do is actually set about building these computers, presumably based on user input. The user would first choose a MOBO, and you’d add it to an empty computer object. Then, the user would choose a CPU, and you’d flag an error message if trying to add that didn’t work, or add it if it did, and life would still be good.
But, let’s say that you want to add some functionality to let the user select some pre-assembled machines and then, perhaps customize them. So, you get the first spec in and, no problem, you add it, perhaps to the presentation layer class for backing the view of the computer building screen (remember, POC 🙂 ):
public Computer BuildBudgetComputer()
{
var myComputer = new Computer();
myComputer.AddMotherboard(new Acer());
myComputer.AddCpu(new Athalon());
myComputer.AddMemory(new Crucial());
return myComputer;
}
There you go – you’ve got a method in the class that builds a budget computer, pre-assembled for selection, allowing users to build their own or take your canned construction. Then, a requirement for another pre-configured one comes in, and you dutifully add it:
public Computer BuildBudgetComputer()
{
var myComputer = new Computer();
myComputer.AddMotherboard(new Acer());
myComputer.AddCpu(new Athalon());
myComputer.AddMemory(new Crucial());
return myComputer;
}
public Computer BuildExpensiveComputer()
{
var myComputer = new Computer();
myComputer.AddMotherboard(new IntelMobo());
myComputer.AddCpu(new Intel());
myComputer.AddMemory(new Crucial());
return myComputer;
}
The two methods look pretty similar, but you’re doing a POC, so you choose not to sweat the small stuff, like “Don’t Repeat Yourself”. Now, each time you want to add new preconfigured computer, you have a design pattern. All you have to do is copy one of those methods and put different objects in the Add() calls.
Now, let’s fast forward a few months. After explaining that this was just a POC to management, management decides to ship the POC since it’s working so well. In the interim, you’ve gotten requirements to add hard drives, USB hubs, monitors, keyboards, mice, etc. You also now have to offer hundreds of pre-built computers to the users, along with the coolness of building their own.
Now, along comes an interesting new requirement. Instead of picking the MOBO first, the users now have to pick the CPU first. So, you switch the method preconditions for the AddCpu() and AddMotherboard() methods in Computer, run your unit tests/application, and discover that nothing works. D’oh! You’re still building them in the same order. You just need to switch the order of the client calls to AddMotherboard() and AddCpu(), and…. oh. You need to go through hundreds of methods doing this. Ouch…
So, What to Do?
At this point, “what to do” requires an examination of “what went wrong?” Everything here was a little slapdash, but relatively reasonably constructed. There was no obvious step where things got out of hand, and yet you suddenly have a mess. As it turns out, you have a mess that was completely avoidable.
Think back to the step where we copy/pasted some code and how that didn’t feel right. Well, turns out it wasn’t right (hint: it’s pretty much never right — Don’t. Repeat. Yourself. ). That little concession early in the game led to big problems later. We duplicated the logic of ordering the construction of the computer over and over again.
What could we have done instead? Well, builder pattern to the rescue. Let’s rewind to when we just had our computer and components, and our first method for a canned computer. Taking another look at that method, we see:
public Computer BuildBudgetComputer()
{
var myComputer = new Computer();
myComputer.AddMotherboard(new Acer());
myComputer.AddCpu(new Athalon());
myComputer.AddMemory(new Crucial());
return myComputer;
}
Looking at this a bit more abstractly, we have a method that accepts no parameters and returns a computer object. Hard-coded in there are various derived polymorphs that are specific to this particular construction. Also hard-coded in there is obeying the temporal coupling between the different add operations. So here, we’re doing two things — defining which components will be added, and defining the order in which they are added. Now, when we define our next implementation via copy/paste, we’re duplicating the ordering logic but giving different specific objects. What if, instead of coupling these two things, we introduced an abstraction:
public Computer BuildBudgetComputer(Motherboard motherboard, Cpu cpu, Memory memory)
{
var myComputer = new Computer();
myComputer.AddMotherboard(motherboard);
myComputer.AddCpu(cpu);
myComputer.AddMemory(memory);
return myComputer;
}
Now, BuildComputer() just encapsulates the logic for navigating the temporal coupling. It’s up to somebody else which components it should be given. This is already a vast improvement and a solution to the problem that we created. Now, client code is making calls like:
public Dictionary BuildPreassembledComputers()
{
var myComputers = new Dictionary();
myComputers["Budget"] = BuildComputer(new Acer(), new Athalon(), new Crucial());
myComputers["Expensive"] = BuildComputer(new IntelMobo(), new Intel(), new Crucial());
return myComputers;
}
public Computer BuildComputer(MotherBoard motherboard, Cpu cpu, Memory memory)
{
var myComputer = new Computer();
myComputer.AddMotherboard(motherboard);
myComputer.AddCpu(cpu);
myComputer.AddMemory(memory);
return myComputer;
}
Now, we’ve solved our main initial problem where the logic for the ordering of computer construction being strewn all over the place. With this code, we have one method to change: BuildComputer(), rather than hundreds.
But, we still have a subtle problem. Recall that we’d talked about now having additional components like monitors and hard drives, and we have hundreds of pre-canned combinations. Think about what BuildPreassembledComputers() is going to look like. It’s going to contain hundreds of permutations of parts, named only by the key in that dictionary. Every time you want to add a new pre-assembled computer, you’re going to have to open that class and add a line to that gargantuan method. If some of the computers become obsolete, you’re going to have to remove them from that method. That is, a the composition of a “budget” computer is going to change over the course of time as new parts come out and old ones become obsolete. Why should your presenter/controller/ViewModel have to change when that happens? At some point, you’re going to have to tie the particular computer to some hard-coded name, but it needn’t be there.
So, let’s extrapolate to something a little closer to the builder pattern.
public abstract class AlmostBuilder
{
public abstract Computer BuildComputer();
protected Computer AssembleComputer(MotherBoard motherboard, Cpu cpu, Memory memory)
{
var myComputer = new Computer();
myComputer.AddMotherboard(motherboard);
myComputer.AddCpu(cpu);
myComputer.AddMemory(memory);
return myComputer;
}
}
public class BudgetBuilder : AlmostBuilder
{
public override Computer BuildComputer()
{
return AssembleComputer(new Acer(), new Athalon(), new Crucial());
}
}
public class ExpensiveBuilder : AlmostBuilder
{
public override Computer BuildComputer()
{
return AssembleComputer(new IntelMobo(), new Intel(), new Crucial());
}
}
public class Client
{
public Dictionary BuildPreassembledComputers()
{
var myComputers = new Dictionary();
myComputers["Budget"] = new BudgetBuilder().BuildComputer();
myComputers["Expensive"] = new ExpensiveBuilder().BuildComputer());
return myComputers;
}
}
Now, we’re onto something. The builder base class encapsulates a method where the temporal coupling is defined exactly once. The derived builders have the responsibility for defining exactly which components are part of the computer that they’ll return. And, the presentation layer class has the responsibility only for presenting things to the user. It knows what kind of computer it wants, and it delegates the construction totally – both the temporal coupling of the construction and the components used in assembly.
The derived classes have only the responsibility of determining which parts will be added to the composite, and the base class’s Assemble() method has the sole responsibility for navigating the assembly particulars. This solves the issue of modifying a method in a presentation class over and over again and an implied violation of the Open/Closed principle. Now, if we want to define a different building paradigm we can define a new class that derives from AlmostBuilder base class. Of course, somewhere or another we will need to add a line of creation code for that, but it won’t be here in the presentation layer, and we probably already have a creational pattern for that somewhere (like a factory).
Now, to get to the bonafide pattern, we define a “Director” class and different Builders expose methods like “BuildMemory()”, “BuildCpu()” and “BuildMotherBoard()”. The Director takes a builder polymorph as an argument and use its different members to assemble the composite.
public class Director
{
public Computer BuildComputer(Builder builder)
{
var myComputer = new Computer();
myComputer.AddMotherboard(builder.BuildMotherboard());
myComputer.AddCpu(builder.BuildCpu());
myComputer.AddMemory(builder.BuildMemory());
return myComputer;
}
}
public abstract class Builder
{
public abstract Memory BuildMemory();
public abstract Cpu BuildCpu();
public abstract MotherBoard BuildMotherboard();
}
public class BudgetBuilder : Builder
{
public override Cpu BuildCpu()
{
return new Athalon();
}
public override Memory BuildMemory()
{
return new Crucial();
}
public override MotherBoard BuildMotherboard()
{
return new Acer();
}
}
public class ExpensiveBuilder : Builder
{
public override Cpu BuildCpu()
{
return new Intel();
}
public override Memory BuildMemory()
{
return new Crucial();
}
public override MotherBoard BuildMotherboard()
{
return new IntelMobo();
}
}
public class Client
{
public Dictionary BuildPreassembledComputers()
{
var myComputers = new Dictionary();
var myDirector = new Director();
myComputers["Budget"] = myDirector.BuildComputer(new BudgetBuilder());
myComputers["Expensive"] = myDirector.BuildComputer(new ExpensiveBuilder());
return myComputers;
}
}
I didn’t show this until just now because I wanted to go through the series of mental leaps and abstractions that would lead us to this point. It may not be necessary at first to launch into an all out implementation. There are steps along the way, and each introduces a new degree of abstraction that’s great and helpful as long as it’s not overkill.
This full blown builder pattern has all of the previous advantages, and the addition of the Director both adds complexity and customizability. You can now inject the director and/or the builders into your presentation class for maximum flexibility.
I’d also like to note that you can use this pattern to do some cool stuff with fluent interfaces. Imagine if the builders, instead of returning their components, all returned new Computer objects (assuming Computer had some kind of copy constructor or clone method). You would have an interface like
var myExpensiveComputer = myBuilder.AddMotherboard(new IntelMobo()).AddCpu(new Intel()).AddMemory(new Crucial());
A More Official Explanation
So pulling back for a moment, let’s consider what we’ve really done here. What got us into trouble was having the methodology of the construction of our composite object, computer, mixed together with the definition of the composite components themselves. To speak plainly, we mixed the how with the what.
According to wikipedia, the main purpose of the Builder Pattern is “to abstract steps of construction of objects so that different implementations of these steps can construct different representations of objects”. Again, this describes separating the how (“construction of objects”) from the what (“different representations of the objects”).
Other Quick Examples
Other examples of using the builder pattern might include:
* You have some object that provides all of the services for your application and you want to supply different services for different runtime configurations, and you want to separate selection of the services from building and instantiating them.
* You’re modeling a pizza delivery service and you want to offer customizable pizzas but also some pre-defined ones that change perhaps by season or specials of the week or something.
* You’re assembling a multi-level composite.
A Good Fit – When to Use
More generally, a good heuristic for when to use the builder pattern is when construction of an object is complicated. In my experience, builder is generally appropriate if and only if you’re building a composite object, though YMMV. So, you might want to consider a builder pattern usage if your composite has temporal coupling as to how it needs to be built. You might also want to do it if your composite can have different configurations.
But, the most important and subtle point here is that you want to do this when there’s some notion of wanting to define discrete assembly characteristics – some assemblies work together but not others according to your business rules. In this sense, you’re separating business rules for valid configuration from the actual nuts and bolts construction of the object.
Square Peg, Round Hole – When Not to Use
Conversely to the idea of discrete assemblies, it’s not a good idea to use this if you have relatively infinite possibilities. For instance, let’s say you defined a “DateRange” struct that consisted of two DateTime objects. You don’t want a builder for this. All other things being equal, the only real restriction on creation of this object is that start date should probably not come after end date. You don’t need a builder to navigate the particulars of construction here — just a simple check in the struct’s constructor. You’d get more into builder territory as the rules for date creation tightened (say, you had a specific application where you could only choose from one of four start dates and one of four end dates or something).
Another bad reason to use this is if you don’t actually need it. We sort of pushed it with the example above — the abstraction of the BuildComputer() method might have sufficed for the requirements as stated up to that point. The pattern is powerful, but it introduces complexity, and it’s important to make sure the complexity isn’t of the needless variety.
And finally, I’d like to emphasize that there is a specific construction mode for where there is a far better design pattern — building polymorphs. Builder is for composites. Don’t use it for a situation where you have a “Food” base class and you read in strings like “IceCream” and “Steak” and create inheritors IceCream and Steak depending on what you read in. That calls for one variation or another of the factory pattern — a pattern specifically for figuring out how to create inheritors of a common base or implementers of a common interface.
So What? Why is this Better?
Object construction is one of those things that tends to creep up on you in terms of complexity. In the book “The Pragmatic Programmer: From Journeyman to Master”, Dave Thomas and Andy Hunt describe the boiling frog phenomenon. The idea is (and the authors swear never to have tried it) that if you put a frog in a boiling pot of water, it will, predictably, leap out. But, if you put it in cold water and increase the temperature gradually over time, the frog will remain there even when the temperature is eventually raised to boiling and cooks the frog.
That is how object creation tends to work in code bases. There is nothing simpler than a “var myClass = new MyClass()” declaration, so why spend a lot of time thinking about where to put it? Eventually, myClass’s constructor gets more complicated, and it picks up some initialization logic and other things, and then you need to create lists and dictionaries of it, and at some point it gets inheritors that you also need to create. Before you know it, your ad-hoc creation of these things is a nightmare.
As shown above, the Builder Pattern alleviates this problem. It separates the logic for providing components for a complicated object from the logic that governs the order of assembly of those components, and it separates both of those things from the logic that actually uses the components. When used in this fashion, changes to any one of those concerns do not affect the other two. And, decoupling and dependency management is the key to avoiding maintenance and feature-addition headaches.
[…] partition the message’s construction into different spheres of responsibility (there’s another pattern for […]