我从来没有真正看过工厂模式,今天决定花时间根据这篇文章(http://msdn.microsoft.com/en-us/library/ee817667.aspx)创建一个快速的样本,最后让我了解它。
源代码完美地安排在三个单独的程序集中,这些程序集名为Product,Factory和Client。
Factory模式的主要好处(据我所知)是从“Client”类抽象出“product”类的实例化。因此,在提供的示例中,无论是否对产品类进行任何更改,Product实例化都不会更改,您仍然必须对客户端类进行更改以传递创建更新产品所需的新值。毕竟这些数据必须来自某个地方?
我读到的另一个例子表明,一旦实现了一个类并且其他类的负载直接使用它,在这里对“product”类所做的更改将需要对这个类的每个实例化进行更改,比如说例如,如果构造函数中需要新变量。
根据我的理解,Factory模式确保此类的实例化永远不会更改,如果要将新变量传递给product构造函数,您最终必须将这些新变量传递给更新的工厂代替。
因此,这显然无法解决问题,只是移动它并且这样做会增加额外的复杂性。
鉴于这是一种既定的模式,我显然错过了一些东西。因此,这篇文章:请向我解释我错过了什么。
由于
答案 0 :(得分:8)
当您可以拥有相同接口的许多不同实现时,使用工厂,并且仅确定客户端实际需要的运行时间。但是,客户端不需要知道它实际使用的是哪个实现。这是工厂介入的地方:它封装了创建具体对象的细节,并将其作为所需接口的通用实现返回。
实际上有两个与名称Factory关联的不同模式:Abstract Factory和Factory Method。后者用于创建单个产品的实例,而前者用于创建整个相关产品系列。
Abstract Factory的一个典型示例是在GUI框架中创建一系列小部件。框架的客户端可能只需要知道他们正在处理窗口,状态栏或按钮;但是,它们无需与实际的小部件实际上是Windows或MacOS小部件相关联。这允许人们创建可以在这些平台上运行的客户端;理论上,当框架移植到一个新平台,比如Linux时,所需要的只是实现一个新工厂,生成所有Linux特定的小部件,并通过配置插入它。请注意,客户端在Linux上运行时没有注意到任何差异,甚至可能无需重新编译客户端代码(至少在理论上,在某些语言中 - 我知道关于多平台GUI的现实是不同的,但这只是一个例子: - )
将此与尝试在没有工厂的情况下实现相同:您将在客户端代码中有许多位置,您需要决定需要创建哪个特定于平台的窗口小部件。每当您想要引入一系列新的小部件时,您需要修改代码中的每个位置,以便将新分支添加到许多相同的switch
或if/else
块中。此外,由于您将公开处理特定于平台的窗口小部件对象,因此某些特定于平台的窗口小部件特性和实现细节可能会泄漏到客户端代码中,从而使得移植到其他平台变得更加困难。
无论产品类的任何更改如何,产品实例化都不会更改,您仍需要对客户端类进行更改以传递创建更新产品所需的新值。毕竟这些数据必须来自某个地方?
事实上。如果常规实例化过程发生更改,则Factory接口可能也需要相应更改。这不是工厂的重点。虽然您可以在构建时将数据传递到工厂,然后在创建新产品时可以在后台使用它。
答案 1 :(得分: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;
...
}
您可以为测试/运行环境声明不同的接线。
答案 2 :(得分:1)
以下是维基百科关于工厂模式的摘录:
创建对象通常需要复杂的过程,这些过程不适合包含在合成对象中。对象的创建可能导致代码的重复,可能需要组合对象无法访问的信息,可能无法提供足够的抽象级别,或者可能不会成为组合对象关注的一部分。
因此,有多种优势,在具体情况下可能都存在,或者只是一些优势。
你是对的,工厂没有解决必须传递必需的构造函数参数的问题。但是,想象一下这样的情况,这些参数需要复杂的计算来确定(您可能需要从数据库中获取值,或类似的东西)。因此,只需创建Product
的实例,就需要在创建此类实例所需的每个位置都使用一些重要的代码。
对于那些复杂的对象实例化,工厂模式会闪耀,因为您的剩余代码将独立于对象创建中涉及的这种复杂性。
这是解释它的另一个粗略想法:对于一个简单的对象,你只需使用new MyClass(some_arg)
。如果实例化显然更复杂,那么您将需要几行代码和可能的其他辅助方法。工厂将此简化为简单的Factory.createMyClass(some_arg)
。
答案 3 :(得分:1)
在C ++中,没有虚拟构造函数。这在Java等语言中仍然不容易。这意味着必须始终确切地知道在创建对象的地方创建了哪种对象。这是插件的一个主要问题,因为它可以防止人们抽象出对象创建代码。
工厂模式解决了这个问题。人们只需要创建一次混凝土工厂。然后,可以在一个通用代码中传递对抽象工厂(从中派生具体工厂)的引用,并在需要创建具体对象时使用它,而不必确切地知道它将创建什么对象。
还有其他好处。由于一个人只有一个创建对象的位置,因此很容易存储所有已创建对象的列表。