问题描述
一个接口(如下图的Product)可能有多种实现方式。程序逻辑在实例化这种类型/接口的具体类(如下图的ConcreteProduct)的时候,如果直接使用{new ConcreteProduct()}的方式来撰写;当需求变更的时候,程序需要使用另外一个子类(例如SubProduct)来替换该类的时候,所有使用{new ConcreteProduct()}的地方都需要修改。这种代码的特征是:没有封装好程序的一个可能的“变化点”(即具体对象创建的可变性)。工厂方法模式把{new ClassName(...)}这种对象实例化的代码封装到一个相对稳定的Factory Method中,让一个可能变化的逻辑(类如何实例化)对于客户而言稳定不变。
工厂方法模式
如下图所示,工厂方法模式为具体对象(ConcreteProduct)的创建提供一个间接层,客户端通过一个稳定的对象创建接口(即工厂方法: Product* ConcreteCreator::CreateProduct())来实例化该对象。当程序逻辑需要使用一个不同于ConcreteProduct的类来定义程序行为时,只需要修改ConcreteCreator类的方法Product* ConcreteCreator::CreateProduct()的实现就可以了。虽然客户端硬编码了一个Factory Method类(即ConcreteCreator类),相对于到处修改{new ClassName(...)},这种硬编码方式让变化集中到了一个变化点,让程序的修改局限在一个可控的范围。
讨论
工厂方法模式的价值在于:
- 封装了程序一个可能的变化点:如何创建一个具体对象。本模式定义了一个对象创建的接口,客户端使用这个接口来创建对象,保证了客户端逻辑的稳定性
- 解除了类的创建者(即类的客户端逻辑)和具体类名字的耦合:不管接口如何子类化,修改ConcreteCreator的具体实现就可以创建不同的具体类
- 强化了基于接口编程原则:工厂方法返回的通常是一个抽象类型(基类指针或者接口引用),这要求客户端逻辑基于这些接口/抽象类型来构建,而不是基于具体类型
下面的代码展示了MFC中使用本模式的一个例子:CreateObject()本质上就是一个工厂方法,可以创建一个不带参数的,支持动态创建的MFC对象。虽然没有抽象基类,但是已经体现了工厂方法模式的本质:通过一个稳定的接口来创建对象,返回一个抽象类型;让对象的创建者和对象的具体类型解耦。
#define _DECLARE_DYNCREATE(class_name) \
_DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject) \
...