12

正确实现依赖注入的方法之一是将对象创建与业务逻辑分开。通常,这涉及使用工厂创建对象。

到目前为止,我从未认真考虑过使用工厂,所以如果这个问题看起来有点简单,我深表歉意:

在我遇到的所有工厂模式示例中,我总是看到没有参数化的非常简单的示例。例如,这是从Misko Hevery出色的How To Think About “new” Operator文章中偷来的 Factory。

类应用程序构建器{
  房屋建造(){
    返回新房子(新厨房(
               新水槽(),
               新洗碗机(),
               新冰箱())
           );
  }
}

但是,如果我希望我建造的每座房子都有一个名字,会发生什么?如果我按如下方式重写此代码,我是否仍在使用工厂模式?

类应用程序构建器{
  房屋建造(const std::string & house_name){
    返回新房子(房子名称,
                      新厨房(新水槽(),
                                  新洗碗机(),
                                  新冰箱())
                    );
  }
}

请注意,我的工厂方法调用已从这里更改:

应用程序构建器;
房子 * my_house = builder.build();

对此:

应用程序构建器;
房子 * my_house = builder.build("Michaels-Treehouse");

顺便说一句:我认为将对象实例化与业务逻辑分离的概念很棒,我只是想弄清楚如何将它应用于我自己的情况。让我感到困惑的是,我看到的所有工厂模式示例都没有将任何参数传递给 build() 函数。

需要明确的是:直到我需要实例化它之前,我才知道房子的名称。

4

7 回答 7

12

我已经看过很多使用一组固定参数的示例,例如您的名字示例,并且我自己也使用过它们,我看不出有什么问题。

然而,许多教程或小文章避免展示将参数转发给构造对象的工厂是有充分理由的:实际上不可能转发任意数量的参数(即使对于像 6 个参数这样的合理限制)。您转发的每个参数都必须被接受const T&T&如果您想要通用的话。

但是,对于更复杂的示例,您需要一组呈指数增长的重载(对于每个参数,一个 const 和一个非常量版本),并且根本不可能进行完美转发(例如,临时对象被作为临时对象转发)。对于下一个 C++ 标准,该问题已解决:

class ApplicationBuilder {
  template<typename... T>
  House *build( T&&... t ) {
    return new House( std::forward<T>(t)...,
                      new Kitchen(new Sink(),
                                  new Dishwasher(),
                                  new Refrigerator())
                    );
  }
};

这样,您可以调用

builder.build("Hello", 13);

它会回来

new House("Hello", 13, new Kitchen(new Sink(...

阅读我上面链接的文章。

于 2009-03-05T16:39:06.460 回答
6

不仅是可以接受的,而且将参数传递给工厂方法也很常见。看看一些例子。通常参数是告诉工厂要做什么的类型,但没有理由不能添加构建对象所需的其他信息。我认为你正在做的很好。

于 2009-03-05T16:34:14.677 回答
5

我不明白为什么将此参数添加到您的工厂是错误的。但请注意,您最终不应添加许多参数,这些参数可能对工厂创建的所有对象都没有用。如果这样做,您将失去很多工厂的优势!

于 2009-03-05T16:28:22.850 回答
4

工厂的想法是它为您提供类/接口的实例,因此传递参数没有任何问题。如果有,那么将参数也传递给 new() 也很糟糕。

于 2009-03-05T16:31:47.210 回答
1

我同意贝努瓦的观点。想想一个创建类似 sql 连接的工厂,在这种情况下,有必要将有关连接的信息传递给工厂。工厂将使用该信息来使用正确的服务器协议等。

于 2009-03-05T16:31:35.480 回答
1

当然,为什么不..!?

传递参数的好处是它允许您隐藏具体对象的实现。例如,在您发布的代码中,您将参数传递给构造函数。但是,您可以更改实现,以便它们通过Initiailze方法传递。通过将参数传递给工厂方法,您可以向调用者隐藏构造和初始化对象的性质。

于 2009-03-05T16:34:45.707 回答
1

看看 Loki::Factory,也有一个非常类似于 Boost 的实现。我经常以不同风格使用的一些示例代码:

typedef Loki::SingletonHolder< Loki::Factory< Component, std::string, Loki::Typelist< const DataCollection&, Loki::Typelist< Game*, Loki::NullType > > > > ComponentFactory;

乍一看这可能有点奇怪,但是让我解释一下这只野兽以及它的真正强大之处。基本上我们创建一个包含工厂的单例,大多数参数用于单例,组件是我们的产品,std::string 是我们的创建 id 类型,之后是创建组件所需的参数类型列表(这可以使用宏定义,也可以使用不那么冗长的语法)。在这一行之后,一个人可以这样做:

ComponentFactory::Instance().CreateObject("someStringAssociatedWithConcreteType", anDataCollection, aGamePointer);

要创建对象,只需使用 ComponentFactory::Instance().Register(); 注册一个对象。《现代 C++ 设计》一书中有一个关于细节的精彩章节。

于 2009-03-05T16:40:50.713 回答