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