Recently I've come across the Builder design pattern. It seems that different authors use "Builder pattern" to refer to different flavours, so let me describe the pattern I'm asking about.
We have an algorithm for creating products, i.e., objects of different types. At a sufficiently high level of abstraction the algorithm is the same for all product types, but each product type requires a different implementation of each of the algorithm's abstract steps. For example, we might have the following cake-baking algorithm:
1. Add liquids.
2. Mix well.
3. Add dry ingredients.
4. Mix well.
5. Pour batter into baking pan.
6. Bake.
7. Return baked cake.
Different cakes would require different implementations of these steps, i.e., what liquids/dry ingredients to use, what speed to mix at, how long to bake, etc.
The pattern says to do it like so. For each product we create a concrete builder开发者_开发百科 class with an implementation for each of the above steps. All of these classes are derived from an abstract builder base class, which is essentially an interface. So, for example, we will have an abstract base class CakeBaker
with pure virtual methods AddLiquid()
, MixLiquids()
, etc. The concrete cake bakers will be concrete subclasses, e.g.,
class ChocolateCakeBaker : public CakeBaker {
public:
virtual void AddLiquids()
{
// Add three eggs and 1 cup of cream
}
virtual void AddDryIngredients()
{
// Add 2 cups flour, 1 cup sugar, 3 tbsp cocoa powder,
// 2 bars ground chocolate, 2 tsp baking powder
}
...
...
};
The LemonCitrusCakeBaker
would also be a subclass of CakeBaker
, but would use different ingredients and quantities in its methods.
The different cake types will similarly be subclasses of an abstract Cake
base class.
Finally, we have a class to implement the abstract algorithm. This is the director. In the bakery example we might call it ExecutiveBaker
. This class would accept (from the client) a concrete builder object and use its methods in order to create and return the desired product.
Here's my question. Why do we need the director to be separate from the abstract builder? Why not roll them into a single builder abstract base class, making the original abstract builder's public methods protected (and the concrete subclasses override these as before).
The core portion of the Builder pattern concerns the Abstract Builder and its subclasses (concrete builders). According to GoF's Design Patterns, director simply "notifies the builder whenever a part of the product should be built", which can be perfectly done by the client.
The StringBuilder class in the Java API is an example of a builder without the respective director -- typically the client class "directs" it.
Also, in Effective Java and Creating and Destroying Java Objects, Joshua Bloch suggests the use of the builder pattern, and he does not include a director.
The GoF variation of the Builder pattern does NOT have the Builder WITHOUT the Director. There's a different point to this, but I'll explain further.
The Builder pattern's point is to give you multiple ways to create the same object. Builder should only have methods which build different parts of an object, but the algorithm - the way these functions are executed - should be the concern of the Director. Without the Director every client would have the need to know EXACTLY how the building works. But with the Director all the Client needs to know is what Builder to use in a specific case.
So, what we have here are two parts:
- The Builder, that creates parts of the object one by one. The important thing to note is that for this it keeps state of the created object.
- The Director, that controls the way Builder`s functions are executed.
Now to the point I was referring earlier. The Builder part of the pattern is useful in other cases and has been used by different vendors WITHOUT the Director for different purposes. A concrete example of such use would be the Doctrine Query Builder.
The disadvantage of such approach is when the Builder starts to build an object it becomes stateful and if the Client doesn't reset the Builder after the object was created - another Client or the same Client that has been used more than once could get the parts of the object that was created earlier. For this reason, Doctrine uses the Factory pattern to create every instance of the Builder.
I hope this helps those googling.
If you separate into Director and Builder you have documented the different responsibility of assembling a product from a set of parts (director) and the responsibility of creating the part (builder).
- In the builder you can change how a part is build. In your case whether a
AddLiquid()
should add cream or milk. - In the director you can change how to assemble the parts. In your case a by using
AddChocolate()
instead ofAddFruits()
you get a different cake.
If you want this extra flexibility, I would rename to (since using baker in the builder suggests, it was the builders job of assembling the parts)
class LightBakingSteps : public BakingSteps {
public:
virtual void AddLiquids()
{
// Add milk instead of cream
}
virtual void AddDryIngredients()
{
// Add light sugar
}
...
};
class ChoclateCakeBaker : public CakeBaker {
public:
Cake Bake(BakingSteps& steps)
{
steps.AddLiquieds();
steps.AddChocolate(); // chocolate instead of fruits
return builder.getCake();
}
}
Builder knows how to do specific steps. Director knows how to assemble the whole thing using builder steps.
They work together.
The only fragility that I can see with this pattern is that client is able to call Builder methods directly without Director - that may bring some issues and incoherence (for example not calling Init method that is part of whole algorithm)
Let's say that you want to make a cake without the dry ingredients. What you are going to do is just adding a new method into the Director or making another Director. This will protect you from inheritance complexity and also will make your code more flexible.
I agree with you. I think that the other approach is that the CakeBaker should have a GetCake() method which returns a cake(Cake class) and MakeCake() method where the algorithm will run. That's fine but on other hand there is a responsible separation there. Consider abstract builder and specific bulders as builders of a cake parts only and Director as manager or designer whose responsibility is to assemble and produce a cake.
Downside of patterns is that they pollute our understanding of business domain with technical terms and blurs our focus.
As I see it - there is too much coupling in between cake and knowledge of how to make it. Those can be decoupled by introducing an idea of cake having a recipe in our code (more like borrowing from real world, designing our model by business domain). Recipe would have ingredients and baking steps (just a step name, not actual implementation because recipes don't bake cakes) on how to make cake what recipe describes. Our baker would have a method BakeCake(recipe), and bunch of smaller methods according to baking steps like mix, add ingredient, etc.
Be aware that if you would need to model chef in general, not just cake baker, you would also need to decouple knowledge of making bakes from baker itself too. It could be done by introducing idea of chef having a skill.
精彩评论