问题
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?
回答1:
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:
- Creating objects that have several valid configurations
- I think your car thing is a great example
- Creating immutable objects where not all required data can be supplied up front
- For a great example of this check out the guava immutable collection builders for instance ImmutableSet.Builder
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();
回答2:
Well,I wondered the same question few months ago and I come up with a new design pattern called step builder pattern
(full description of this pattern here).
In this design I give the possibility to design paths to take, avoiding the need to create multiple builders.
The concept is simple:
- Write creational steps in inner classes or interfaces where each method knows what can be displayed next.
- Implement all your steps interfaces in an inner static class.
- Last step is the BuildStep, in charge of creating the object you need to build.
回答3:
The Builder pattern that you will usually see (especially in Java) is a single Builder for a given object, that has a fluid interface for setting the different parameters. This is the pattern advocated in Effective Java, and that is why you will generally see that in Java.
If you have some very different building scenarios, different builder objects might make sense, of course, but I would take a step back here. If you have so many different properties in one object, you may have an object doing too much, and a symptom of the problem is the complexity of the builders.
Of course real world objects are complicated, but the basic thesis of Object Oriented design is that if the complexity is layered in abstractions so that at any given layer you are dealing with 5 to 7 properties at a time, you can limit the problem from becoming too complex to understand at a given layer.
The difficulty of properly designing the layers of abstraction make the above as much idealism as reality, but I think the point of having an Object do too much stands.
In your car example, there are many layers to the car, but the trick to keeping that manageable will be to differentiate the different assemblies and have the larger car built of a few assembly options which are themselves built of a few assembly options, etc. Each assembly would merit its own object, and potentially its own builder.
回答4:
It seems to me like you are using too many builders and each builder does not do enough. You should have one builder for each class. You seem to be trying to create a new builder for each set of field values. Instead, each field should have a method or two within a single builder. Your builder calls should end up looking something like this
Car c = new SUVBuilder().withSunroof().withColor("Red").build();
The above assumes that SUV is a subclass of car. If it isn't, then you my be able to specify SUV in an additional function.
回答5:
Builders allow you to encapsulate a complex series of choices into a straightforward algorithm. For example, if your AssemblyLine is building a car, it might:
buildChassis();
buildDriveTrain();
buildBody();
assemble();
paint();
You might want separate concrete builders for different chassis, drive trains, and bodies, but you can probably get away with just one for paint--you just parameterize it for color in the c'tor. Your director class knows what the details of your builder are, so it instantiates the right concrete builders; your assembly-line client just runs the steps in order. You can parameterize the concrete builders for any details which aren't fundamental (and what's fundamental is a judgement call).
来源:https://stackoverflow.com/questions/4737836/can-the-builder-pattern-ever-be-doing-too-much