2011-01-28 60 views
3

我从来没有真正看过工厂模式,今天决定花费时间,并根据这篇文章(http://msdn.microsoft.com/en-us/library/ee817667.aspx)创建一个快速样本,最终让我的头。工厂模式为何如此工作?

源代码完美地安排在三个单独的组件中,整齐地命名为Product,Factory和Client。

工厂模式的主要好处(据我所知)是从“客户”类抽象“产品”类的实例化。因此,在所提供的示例中,产品实例化不会随产品类的任何更改而发生变化,您仍然必须对客户端类进行更改以传递创建更新产品所需的新值。这些数据毕竟必须来自某个地方?

我读过的另一个例子说,一旦一个类被实现并且其他类的负载直接使用它,在这里对“产品”类所做的更改将需要对该类的每个实例进行更改,比如说例如,如果在其构造函数中需要一个新变量。

从我所能理解的情况来看,Factory模式确实可以确保这个类的实例永远不会改变,如果你想将一个新变量传递给产品构造函数,那么你最终不得不将这些新变量传递给更新改为工厂。

因此,这显然不能解决问题,但仅仅移动它并且这样做会增加额外的复杂性。

鉴于这是一种既定的模式,我显然缺少一些东西。因此,这篇文章:请向我解释我缺少的东西。

感谢

回答

8

工厂使用的时候可以有相同的接口的许多独特的实现,它是决定只运行其中一个客户的实际需要。但是,客户端不需要知道它实际使用的是哪个实现。这是Factory进入的地方:它封装了创建具体对象的细节,并将其作为所需接口的通用实现返回。

实际上有两种不同的模式与名称Factory相关:Abstract FactoryFactory Method。后者用于创建单一产品的实例,而前者用于创建一整套相关产品。

抽象工厂的典型示例是在GUI框架中创建一系列小部件。框架的客户端可能只需要知道他们正在处理一个窗口,一个状态栏或一个按钮;然而,它们不需要与实际小部件实际上是Windows还是MacOS小部件相关联。这允许创建可以在这些平台上运行的客户端;在理论上,当框架被移植到新的平台,比如说,Linux的,所有需要的是实现一个新的工厂产生的所有Linux特有的小部件,并通过配置插入。你瞧,客户在Linux上运行没有注意到任何区别,甚至可能无需重新编译客户端代码(至少在理论上,而且在某些语言 - 我知道,对于多平台的GUI现实是不同的,但是这仅仅是:-)

的例子比较这对试图实现同样的无工厂:在这里你需要决定你需要建立一个特定于平台的插件,你必须在客户端代码中的许多地方。并且,无论何时您想要引入新的小部件系列,您都需要修改代码中的这些位置,以便为许多相同的switchif/else块添加新分支。此外,由于您会公开处理特定于平台的窗口小部件对象,因此某些特定于平台的窗口小部件的特性和实现细节可能泄漏到客户端代码中,因此更难以移植到其他平台。

无论产品类别发生什么变化,产品实例化都不会改变,您仍然需要对客户端类别进行更改以传递zin创建更新产品所需的新值。这些数据毕竟必须来自某个地方?

确实。如果一般实例化过程改变,那么Factory接口也可能需要相应地改变。这不是Factory的要点。尽管您可以在构建时将数据传递给工厂,但只要创建新产品,就可以在后台使用这些数据。

+0

这也可能是很好的补充说,工厂模式通常处理相关类型的层次结构的创建,而不仅仅是一个。 – Grozz 2011-01-28 13:09:28

1

下面是在工厂模式维基的摘录:

对象的创建往往需要复杂的工艺不适合于构成对象内包括。对象的创建可能会导致代码的重复,可能需要组成对象无法访问的信息,可能无法提供足够的抽象级别,或者可能不会成为组成对象关注的一部分。

所以有多层次的优势,在具体情况下可能都存在,或者只是一些。

你是对的,一个工厂没有解决必须传递所需构造函数参数的问题。然而,设想一个案例,其中这些参数需要复杂的计算来确定(您可能需要从数据库中获取值,或类似的东西)。因此,只需创建Product的实例,就需要在创建此类实例所需的每个位置都有相当一部分代码。

对于那些复杂的对象实例,工厂模式发光,因为剩余的代码与对象创建过程中涉及的复杂性无关。

下面是另一个粗略的想法来解释它:对于一个简单的对象,你只需使用new MyClass(some_arg)。如果实例化显着更复杂,则需要多行代码和可能的其他辅助方法。该工厂将其缩减回简单的Factory.createMyClass(some_arg)

3

使用工厂是Dependency Inversion的一种形式,这是一种将客户端与实现分离的方式。

例如,请考虑以下几点:

class Client { 
    private DatabaseReader reader = new DatabaseReader(); 
    public void read() { 
     reader.read(); 
    } 
} 

凡DatabaseReader是一个具体的类。让我们试着通过定义一个接口打破这种耦合:

class Client { 
    private Reader reader = new DatabaseReader(); 
    ... 
} 

几乎有:

class Client { 
    private Reader reader = ReaderFactory.getInstance.getReader(); 
    ... 
} 

现在,客户端不关心,如果它得到DatabaseReader,MemoryReader,等...它成为ReaderFactory负责提供合适的Reader。

依赖注入需要更进一步,不再需要加载Factory类,而是在依赖注入容器中启动代码。

class Client { 
    @Inject 
    private Reader reader; 
    ... 
} 

在哪里可以为测试/运行环境声明不同的接线。

1

在C++中,没有虚拟构造函数。在Java这样的语言中,这仍然不容易。这意味着人们必须始终确切地知道在创建对象时创建了哪种对象。这是插件的一个主要问题,因为它阻止了将对象创建代码抽象出来。

工厂模式解决了这个问题。一个只需要创建一次混凝土工厂。然后,人们可以通过对泛型代码的抽象工厂(从中派生出具体工厂)的引用,并在需要创建具体对象时使用它,而无需确切知道它将创建的对象。

还有其他的好处。由于只有一个创建对象的位置,因此很容易存储所有创建对象的列表。