代码之家  ›  专栏  ›  技术社区  ›  Ari

建筑设计模式:为什么我们需要一个导演?

  •  31
  • Ari  · 技术社区  · 14 年前

    最近我遇到了生成器设计模式。似乎不同的作者使用“构建器模式”来指代不同的风格,所以让我描述一下我要问的模式。

    我们有一个算法来创建 产品 ,即不同类型的对象。在足够高的抽象级别上,算法对于所有产品类型都是相同的,但是每种产品类型都需要不同的算法抽象步骤的实现。例如,我们可能有以下蛋糕烘焙算法:

     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 也会是 凯贝克 ,但在其方法中会使用不同的成分和数量。

    不同的蛋糕类型类似于抽象的子类 Cake 基类。

    最后,我们有一个类来实现抽象算法。这是 主任 . 在面包店的例子中,我们可以称之为 ExecutiveBaker . 这个类将(从客户机)接受一个具体的生成器对象,并使用其方法来创建和返回所需的产品。

    这是我的问题。为什么我们需要导演和抽象建设者分开?为什么不把它们放到一个单独的生成器抽象基类中,使原来的抽象生成器的公共方法受到保护(而具体的子类会像以前一样覆盖这些方法)。

    7 回复  |  直到 6 年前
        1
  •  22
  •   izilotti    12 年前

    构建器模式的核心部分涉及抽象构建器及其子类(具体构建器)。根据 GoF's Design Patterns ,director只需“在产品的某个部分需要构建时通知构建者”,这可以由客户完美地完成。

    这个 StringBuilder JavaAPI中的类是一个没有相应控制器的构建器示例——通常是客户机类“指导”它。

    而且,在 Effective Java Creating and Destroying Java Objects ,Joshua Bloch建议使用构建器模式,他不包括一个导演。

        2
  •  12
  •   drakonli    5 年前

    建设者模式的GoF变体没有指导者就没有建设者。有不同的观点,但我会进一步解释。

    构建器模式的重点是为您提供多种创建同一对象的方法。Builder只应该有构建对象不同部分的方法,但是算法——这些函数的执行方式——应该是控制器关注的问题。如果没有主任,每一个客户都需要确切地知道这座大楼是如何工作的。但是对于Director,客户需要知道的是在特定情况下使用什么构建器。

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

    1. 生成器,它逐个创建对象的各个部分。需要注意的重要一点是,它保持了创建对象的状态。
    2. 控制器,控制生成器函数的执行方式。

    现在我要说的是。模式的Builder部分在其他情况下很有用,并且在没有Director的情况下被不同的供应商用于不同的目的。这种用法的一个具体例子是 Doctrine Query Builder .

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

    我希望这有助于那些谷歌搜索。

        3
  •  9
  •   Micha Wiedenmann Lieven Keersmaekers    12 年前

    如果您分为Director和Builder,那么您已经记录了从一组部件(Director)组装产品的不同责任和创建部件(Builder)的责任。

    • 在生成器中,可以更改零件的生成方式。在你的情况下 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();
         }
    }
    
        4
  •  2
  •   NoDataDumpNoContribution    10 年前

    假设你想做一个没有干原料的蛋糕。你要做的只是在导演中加入一个新的方法,或者换一个导演。这将保护您免受继承复杂性,也将使您的代码更加灵活。

        5
  •  0
  •   Arseny    14 年前

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

        6
  •  0
  •   Aleksander Bethke    8 年前

    生成器知道如何执行特定步骤。 Director知道如何使用builder步骤组装整个东西。

    他们一起工作。

    这个模式唯一的弱点是,客户端可以直接调用Builder方法而不使用Director——这可能会带来一些问题和不一致(例如,不调用Init方法,它是整个算法的一部分)

        7
  •  -1
  •   Arnis Lapsa    12 年前

    模式的缺点是,它们用技术术语污染了我们对业务领域的理解,模糊了我们的注意力。

    在我看来,蛋糕和制作知识之间有太多的耦合。通过在我们的代码中引入蛋糕有配方的思想(更像是从现实世界中借鉴,按业务领域设计我们的模型),可以将这些分离开来。配方将有配料和烘焙步骤(只是一个步骤名称,而不是实际的实现,因为配方不烘焙蛋糕)如何使蛋糕配方描述。我们的面包师将有一个方法BakeCake(配方),并根据烘焙步骤,如混合,添加配料等一系列较小的方法。

    要知道,如果你需要在一般厨师模型,而不仅仅是蛋糕面包师,你也需要脱钩的知识,使烘焙面包师本身。这可以通过引入厨师有技能的概念来实现。