In Object Oriented languages and particularly in Java the Builder pattern is very popular at the moment. Originally designed to handle object construction challenges (Wikipedia uses the term telescoping constructor anti-pattern) there seems to be a trend to implement a builder as the default pattern for objects that have construction variations.
Typical builder use:
Car car = new CarBuilder().withWheels(WheelType.CHROME).color(CarColor.RED).build();
So far so good but this does not really show the benefits of the pattern. I could quite easily construct a car with two constructor parameters.
Car car = new Car(WheelType.CHROME, CarColor.RED);
Adding more attributes to Cars starts to show how the builder can help manage more parameters during construction.
Car car = new CarBuilder() .withWheels(WheelType.CHROME) .color(CarColor.RED) .withSunroof() .withTowbar() .withMirrors(MirrorType.ELECTRIC) .build();
As we add methods to the builder we also need to update the construction
Car car = new Car(); car.setWheelType(wheelType); car.setTowbar(towbar); car.setSunroof(sunroof); car.setMirrors(mirrorType); return car;
We want to make sure that we have valid objects during but more importantly after construction. A builder is mutable the thing we build is mutable during construction but could be immutable after construction by making all the setters protects/private to the builder - a nice benefit.
Which object should be responsible for validating that the supplied parameters make sense. When we building an object through its constructor we expect a valid object or an exception during construction.
The same should be true upon completion of the
build() method but it
is not clear if the builder is responsible for validating that the
object is valid or if the object we are building validates its state.
Making the object we are building (e.g. Car) responsible for validation aligns nicely with constructor construction but conflicts with the concept of a builder putting together valid objects.
Anti-pattern: Validation Envy.
Spreading validation logic across multiple classes makes it harder to understand the rules.
Pick one class to understand what is valid and what is not. If you build mutable objects then they will need to validate changes
Cars need engines and engines are complex in their own right. We could
add the engine parameters to the
builder.pattern.Car car = new CarBuilder2() .withWheels(WheelType.CHROME) .color(CarColor.RED) .withSunroof() .withTowbar() .withMirrors(MirrorType.ELECTRIC) .withEngineType(EngineType.PETROLIUM_SPIRIT) .engineCylinders(16) .build();
If we map the builder construction over to the built objects then we end up with a single Car class. From an object oriented design perspective this is a poor model. A better model would be to have an engine class for engine related data and behavior.
The builder could be enhanced to create a Car object that contains an Engine. Or we could create a builder for engines.
builder.pattern.Car car = new CarBuilder3() .withWheels(WheelType.CHROME) .color(CarColor.RED) .withSunroof() .withTowbar() .withMirrors(MirrorType.ELECTRIC) .withEngine() .withEngineType(EngineType.PETROLIUM_SPIRIT) .engineCylinders(16) .buildEngine() .build();
Builders are linear constructs relying on method chaining. Breaking up the method calls onto separate lines improves readability but the sequence of calls is still linear. Our car structure is hierarchical - Cars have Engines.
The builder structure with a nested build does not match our target structure - there is disconnect between the models.
We could break out a separate builder and pass the result into the parent builder. This helps with structure but if there are inter-dependencies between the classes we are back to a linear call sequence.
Anti-pattern: Structure Misalignment
Make sure that the builder structure and the target structure are in alignment. A linear builder for a hierarchical structure creates a tension between the two models. The builder layout is linear but the resulting built object is not.
The builder pattern helps build sparsely populated objects that need to be initialized in many different ways. Outside of this context there are other patterns than should be considered.
Create objects that represent logical field groups within the parent. Compose the parent object through construction. Variation can be handled within each composite.
In many languages passing a hash-map of key-value pairs that mirrors the target object graph is a simpler construction method. It can be sparse to match the problem and focuses validation into the constructed object. (Building hash-maps in Java is still a problem).
Match the design to the problem. The builder work well for linear construction but breaks down for complex, deep object graphs.
Introduce when constructor complexity becomes unmanageable - can’t compose parameters into smaller object parameters. Rule of thumb 7 parameters for constructors.
Use strongly typed parameters over primitives. Varargs for lists.