最近我遇到了Builder设计模式。似乎不同的作者使用“Builder模式”来引用不同的风格,所以让我描述一下我所询问的模式。
我们有一种用于创建产品的算法,即不同类型的对象。在足够高的抽象级别,所有产品类型的算法都是相同的,但每种产品类型都需要对每个算法的抽象步骤进行不同的实现。例如,我们可能有以下蛋糕烘焙算法:
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.
不同的蛋糕将需要这些步骤的不同实施方式,即,使用什么液体/干燥成分,混合的速度,烘烤多长时间等。
模式说是这样做的。对于每个产品,我们创建一个具体的构建器类,其中包含上述每个步骤的实现。所有这些类都派生自一个抽象构建器基类,它本质上是一个接口。因此,例如,我们将有一个抽象基类CakeBaker
与纯虚拟方法AddLiquid()
,MixLiquids()
等。混凝土蛋糕面包师将是具体的子类,例如,
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
}
...
...
};
LemonCitrusCakeBaker
也将是CakeBaker
的子类,但在其方法中将使用不同的成分和数量。
不同的蛋糕类型同样是抽象的Cake
基类的子类。
最后,我们有一个类来实现抽象算法。这是导演。在面包店的例子中,我们可以称之为ExecutiveBaker
。此类将接受(来自客户端)具体的构建器对象并使用其方法来创建和返回所需的产品。
这是我的问题。为什么我们需要导演与抽象构建器分开?为什么不将它们转换为单个构建器抽象基类,使原始抽象构建器的公共方法受到保护(具体子类将像以前一样覆盖它们)。
Builder模式的核心部分涉及Abstract Builder及其子类(具体构建器)。根据GoF's Design Patterns的说法,导演只是“在建造产品的一部分时通知建筑商”,这可以由客户完美地完成。
Java API中的StringBuilder类是没有相应控制器的构建器的示例 - 通常客户端类“指示”它。
此外,在Effective Java和Creating and Destroying Java Objects,Joshua Bloch建议使用建造者模式,他不包括导演。
如果您分别进入Director和Builder,您已经记录了从一组零件(主管)组装产品的不同责任以及创建零件(构建器)的责任。
AddLiquid()
是否应添加奶油或牛奶。AddChocolate()
而不是AddFruits()
你得到一个不同的蛋糕。如果你想要这种额外的灵活性,我会重命名为(因为在构建器中使用baker建议,这是组装零件的构建者工作)
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模式的GoF变体没有Director而没有Director。对此有不同的看法,但我会进一步解释。
Builder模式的目的是为您提供多种方法来创建相同的对象。 Builder应该只有构建对象不同部分的方法,但算法 - 这些函数的执行方式 - 应该是Director的关注点。如果没有导演,每个客户都需要完全了解建筑的工作原理。但是对于Director,客户需要知道的是在特定情况下使用的Builder。
那么,我们这里有两个部分:
现在到了我之前提到的那一点。模式的Builder部分在其他情况下很有用,并且不同的供应商使用它而不是Director用于不同的目的。这种用途的具体例子是Doctrine Query Builder。
这种方法的缺点是,当Builder开始构建一个对象时,它变为有状态,如果客户端在创建对象后没有重置Builder,则另一个客户端或多次使用的同一个客户端可以获得这些部件之前创建的对象。因此,Doctrine使用Factory模式创建Builder的每个实例。
我希望这有助于谷歌搜索。
假设你想要在没有干成分的情况下制作蛋糕。你要做的只是在Director中添加一个新方法或者另一个Director。这将保护您免受继承复杂性的影响,并且还可以使您的代码更加灵活。
我同意你的看法。我认为另一种方法是CakeBaker应该有一个GetCake()方法,它返回一个蛋糕(Cake类)和MakeCake()方法,算法运行。那很好,但另一方面,那里有一个负责任的分离。考虑抽象建筑商和特定的建筑商作为蛋糕零件的建造者和Director作为经理或设计师,其职责是组装和生产蛋糕。
Builder知道如何执行特定步骤。 Director知道如何使用构建器步骤组装整个事物。
他们一起工作。
我可以用这种模式看到的唯一脆弱性是客户端能够在没有Director的情况下直接调用Builder方法 - 这可能会带来一些问题和不连贯性(例如,不调用作为整个算法一部分的Init方法)
模式的缺点是他们用技术术语污染了我们对业务领域的理解,并模糊了我们的注意力。
正如我所看到的那样 - 蛋糕和如何制作它的知识之间存在太多的耦合。通过在我们的代码中引入一个带有配方的蛋糕的概念(更像是从现实世界借用,按业务领域设计我们的模型),可以将它们分离。食谱将有成分和烘焙步骤(只是一个步骤名称,而不是实际的实施,因为食谱不烘烤蛋糕)如何制作蛋糕什么食谱描述。我们的面包师会有一个方法BakeCake(配方),并根据烘焙步骤,如混合,添加成分等一堆较小的方法。
请注意,如果您需要一般模型厨师,而不仅仅是蛋糕面包师,您还需要将烘焙知识与烘焙师本身分开。可以通过介绍具有技能的厨师的想法来完成。