I've been studying design patterns with a study group recently, and have come to understand that the builder pattern can be very useful for creating complex objects that are made up of many (potentially optional) parts.
However, is there ever a point where the builder is doing too much? Let's say we have a class that has many many different combinations of objects, is there another pattern that may be better suited for that instead of making dozens of different builders? Is it possible to reduce the number of builders you need by not making completely specific builders?
The example my study group and I kept coming back to was a car builder, such as on a car company's website. Any car company has dozens of cars, each with many different features, colors, extras etc. The way I understand it, your builder should be specific to the exact object you are making, so applying a builder pattern to this example would yield hundreds of builders that look like "RedSUVWithSunroofBuilder", "BlueSUVWithSunroofBuilder", "RedSUVBuilder" etc.
Is there any reason that, using the builder pattern, I couldn't pass in some of these values to reduce the number of builders that I would need to create? For example, instead of having RedSUVWithSunroofBuilder, or BlueSUVWithSunroofBuilder, is it still fitting of the builder pattern to do SUVWithSunroofBuilder("Red") and SUVWithSunroofBuilder("Blue"), or is this more fitting of a different pattern?
The builder pattern is certainly discretionary, if it is overly complicated then it is overly complicated, and you may want to consider a different way to create objects, like the factory pattern. I think there are a few scenarios where the builder pattern excels:
Here's one example of how you could implement a car builder:
public class Car {
private final boolean hasSunroof;
private final Color color;
private final int horsePower;
private final String modelName;
private Car(Color color, int horsePower, String modelName, boolean hasSunroof) {
this.color = color;
this.horsePower = horsePower;
this.hasSunroof = hasSunroof;
this.modelName = modelName;
}
public static Builder builder(Color color, int horsePower) {
return new Builder(color, horsePower);
}
public static class Builder {
private final Color color;
private final int horsePower;
private boolean hasSunroof;
private String modelName = "unknown";
public Builder(Color color, int horsePower) {
this.color = color;
this.horsePower = horsePower;
}
public Builder withSunroof() {
hasSunroof = true;
return this;
}
public Builder modelName(String modelName) {
this.modelName = modelName;
return this;
}
public Car createCar() {
return new Car(color, horsePower, modelName, hasSunroof);
}
}
}
The Builder doesn't have to be a nested class, but it does allow you to hide your constructors from people who might misuse your API. Also notice that the minimum required parameters must be supplied to even create a builder. You could use this builder like so:
Car car = Car.builder(Color.WHITE, 500).withSunroof().modelName("Mustang").createCar();