2010-11-30 49 views
24

最近我遇到了Builder的设计模式。似乎不同的作者使用“生成器模式”来指代不同的口味,所以让我描述一下我所问的模式。生成器设计模式:为什么我们需要一个Director?

我们有一个算法来创建产品,即不同类型的对象。在足够高的抽象层次上,所有产品类型的算​​法都是相同的,但是每种产品类型都需要对算法的每个抽象步骤进行不同的实现。例如,我们可能有以下的制饼算法:

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。这个类将接受(从客户端)一个具体的构建器对象并使用它的方法来创建和返回所需的产品。

这是我的问题。为什么我们需要导演与抽象建筑师分开?为什么不将它们转换为单个构建器抽象基类,使得原始抽象构建器的公共方法受到保护(并且具体的子类像前面一样覆盖它们)。

回答

15

Builder模式的核心部分涉及Abstract Builder及其子类(具体构建器)。根据GoF's Design Patterns,导演简单地“每当应该建立产品的一部分时通知建造者”,这可以由客户完全完成。

Java API中的StringBuilder类是没有相应导向器的构建器的示例 - 通常是客户端类“指示”它。

此外,在Effective JavaCreating and Destroying Java Objects,约书亚布洛赫建议使用生成器模式的,他不包括董事。

0

我同意你的意见。我认为另一种方法是CakeBaker应该有一个GetCake()方法,该方法返回一个蛋糕(Cake类)和MakeCake()方法,该算法将运行。这很好,但另一方面,那里是负责任的分离。考虑抽象的建设者和具体的建设者作为一个蛋糕部分的建设者,导演作为负责组装和生产蛋糕的经理或设计师。

+0

责任的分离让我不服气。在某些情况下,分离可能是有意义的;在别人不是。我不认为这是模式的一部分,而是作为一个正交决定。 – Ari 2010-12-01 08:39:40

+0

学习DP我试图让他们的原则不会直接复制它们。而且在实践中我经常把它们组合起来。因此,不需要像在doc中那样编写相同数量的类或接口,而是使您的代码更加灵活和可修改。 – Arseny 2010-12-01 08:52:24

8

如果您分为导演和生成器,您已经记录了从一组部件(导演)组装产品的不同责任以及创建部件(构建器)的责任。

  • 在构建器中,您可以更改零件的构建方式。在你的情况下,AddLiquid()是否应该加奶油或牛奶。
  • 在导演中,您可以更改如何组装零件。在你的情况下,通过使用AddChocolate()而不是AddFruits()你会得到一个不同的蛋糕。

如果你想要这个额外的灵活性,我会重命名为(因为在构建器使用面包师建议,这是组装部件的建设者的工作)

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(); 
    } 
} 
+1

但是这种分离的优点是什么?如果我需要不同的汇编协议,我需要有不同的导演,每个导演都适合特定的(抽象)构建器。这些导演不会共享任何可以抽象的通用功能,除了MakeSomething(),这些功能没有用处。您可以随时将任务分解为更小的子任务和职责,并且您可以随时在任何模式中添加另一级别的“经理”或“主管”。我在想,这种模式的作者可能正在按照您所概述的方式思考,但我看不到其中的价值。 – Ari 2012-12-11 12:17:09

+0

@Ari,你不能看到它的价值,因为编程模式的价值与你的程序所关注的领域固有的链接。为什么每个蛋糕都应该有特定的面包师?拿走领域 - 这只是一个噪音,屏幕上的随机字符。这就是我在答案中的意思。 – 2012-12-11 12:32:37

-1

下行的模式是他们污染我们用专业术语理解业务领域,模糊了我们的焦点。

正如我所看到的那样 - 蛋糕和知道如何制作它们之间的耦合太多。通过在我们的代码中引入一个有配方的蛋糕的想法(更像是从现实世界借用,通过业务领域设计我们的模型),可以将这些解耦。食谱会有配料和烘焙步骤(只是一个步骤名称,而不是实际的实施,因为食谱不烘烤蛋糕)如何做蛋糕什么配方描述。我们的面包师会根据烘焙步骤(如混合,添加配料等)制作一种方法BakeCake(配方),以及一些较小的方法。

请注意,如果您需要一般模型厨师,而不仅仅是蛋糕面包师,你也需要将制作面包师的知识与面包师本身分开。这可以通过介绍有技能的厨师的想法来完成。

2

假设你想做一个没有干成分的蛋糕。你要做的只是在导演中添加一个新的方法或制作另一个导演。这将保护您免受继承复杂性的影响,并且可以让您的代码更加灵活。

4

Builder模式的GoF变体没有Builder,没有Director。这有点不同,但我会进一步解释。

构建模式的重点是为您提供多种方式来创建相同的对象。生成器应该只有构建对象的不同部分的方法,但算法(这些函数的执行方式)应该是Director的关注点。没有主任,每个客户都需要明确知道建筑物的工作方式。但是,所有客户需要知道的是Director在特定情况下使用的Builder。

所以,我们在这里有两个部分组成:

  1. 生成器,由一个创建的对象一个部分。重要的是要注意的是,他保持创建的对象的状态。
  2. 导演,控制建造者的执行。

现在我刚才提到的点。该模式的构建器部分在其他情况下非常有用,并且已被不同供应商使用,而没有出于不同目的的Director。这种使用的具体例子是Doctrine Query Builder

这种方法的缺点是,当构建器开始构建一个对象时,它变为有状态的,并且如果客户端在创建对象后未重置构建器 - 另一个客户端或已使用多次的相同客户端可以获取之前创建的对象的部分。为此,Doctrine使用Factory模式创建Builder的每个实例。

我希望这可以帮助那些使用谷歌搜索。

0

Builder知道如何执行特定步骤。 导演知道如何使用生成器步骤来组装整个东西。

他们一起工作。

,我可以用这个模式中看到的唯一的脆弱性是客户端能够直接调用生成器的方法,而不主任 - 这可能会带来一些问题和不连贯(例如不调用初始化方法,它是整个算法的一部分)

相关问题