Builder设计模式:为什么我们需要Director?

发布于 2024-10-05 03:53:32 字数 1302 浏览 2 评论 0原文

最近我遇到了 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”。此类将接受(来自客户端)具体的构建器对象并使用其方法来创建和返回所需的产品。

这是我的问题。为什么我们需要将主管与抽象构建者分开?为什么不将它们放入单个构建器抽象基类中,使原始抽象构建器的公共方法受到保护(并且具体子类像以前一样覆盖这些方法)。

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).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

回忆那么伤 2024-10-12 03:53:32

构建器模式的核心部分涉及抽象构建器及其子类(具体构建器)。根据 GoF 的设计模式,director 只是“每当需要构建产品的一部分时通知构建者”,这可以通过客户。

Java API 中的 StringBuilder 类是一个构建器示例,没有相应的导演——通常是客户端类“指挥”它。

另外,在 Effective Java创建和销毁 Java 对象,Joshua Bloch 建议使用构建器模式,并且他不包括导演。

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.

一身仙ぐ女味 2024-10-12 03:53:32

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

构建器模式的要点是为您提供多种方法来创建同一对象。 Builder 应该只具有构建对象不同部分的方法,但算法(这些函数的执行方式)应该是 Director 关心的问题。如果没有总监,每个客户都需要确切地了解建筑物的运作方式。但是对于Director,客户需要知道的是在特定情况下使用什么Builder。

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

  1. Builder,它一一创建对象的各个部分。需要注意的重要一点是,为此它会保留创建对象的状态。
  2. Director,控制 Builder 函数的执行方式。

现在回到我之前提到的这一点。该模式的 Builder 部分在其他情况下很有用,并且已被不同的供应商在没有 Director 的情况下用于不同的目的。此类使用的具体示例是 学说查询生成器

这种方法的缺点是,当构建器开始构建对象时,它就会变得有状态,并且如果客户端在创建对象后没有重置构建器 - 另一个客户端或已多次使用的同一客户端可以获得部件之前创建的对象的名称。因此,Doctrine 使用工厂模式来创建 Builder 的每个实例。

我希望这对那些谷歌搜索的人有所帮助。

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:

  1. 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.
  2. 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.

鲜血染红嫁衣 2024-10-12 03:53:32

如果您分为总监和建造者,则您已记录了从一组零件组装产品的不同责任(总监)和创建零件的责任(建造者)。

  • 在构建器中,您可以更改零件的构建方式。根据您的情况,AddLiquid() 是否应该添加奶油或牛奶。
  • 在director中,您可以更改部件的组装方式。在您的情况下,通过使用 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();
     }
}

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 of AddFruits() 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();
     }
}
无名指的心愿 2024-10-12 03:53:32

Builder 知道如何执行特定步骤。
总监知道如何使用构建器步骤组装整个东西。

他们一起工作。

我在这种模式中看到的唯一弱点是客户端能够在没有Director的情况下直接调用Builder方法 - 这可能会带来一些问题和不连贯(例如不调用作为整个算法一部分的Init方法)

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)

转角预定愛 2024-10-12 03:53:32

假设您想做一个没有干原料的蛋糕。你要做的只是在Director中添加一个新方法或者创建另一个Director。这将保护您免受继承复杂性的影响,并使您的代码更加灵活。

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.

獨角戲 2024-10-12 03:53:32

我同意你的看法。我认为另一种方法是 CakeBaker 应该有一个 GetCake() 方法,它返回一个蛋糕(Cake 类)和 MakeCake() 方法,算法将在其中运行。这很好,但另一方面,那里有一个负责任的分离。将抽象构建者和特定构建者视为仅蛋糕部件的构建者,将总监视为经理或设计师,其职责是组装和生产蛋糕。

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.

谷夏 2024-10-12 03:53:32

模式的缺点是它们会用技术术语污染我们对业务领域的理解并模糊我们的焦点。

在我看来,蛋糕和制作蛋糕的知识之间存在太多的耦合。这些可以通过在我们的代码中引入具有食谱的蛋糕的想法来解耦(更像是从现实世界借用,按业务领域设计我们的模型)。食谱将包含配料和烘焙步骤(只是步骤名称,不是实际实施,因为食谱不烘焙蛋糕),说明如何制作食谱所描述的蛋糕。我们的面包师将有一个方法 BakeCake(recipe),以及根据烘焙步骤的一系列较小的方法,例如混合、添加成分等。

请注意,如果您需要为一般厨师建模,而不仅仅是蛋糕面包师,您还需要将烘焙知识与面包师本身脱钩。这可以通过引入厨师拥有技能的想法来完成。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文