一、概述
Factory(工厂)模式用于封装对象的创建过程,将对象的创建独立成单独的程序模块,从而提高整个应用系统的Flexibility。
二、结构
主要有以下三种Factory模式:
1、Simple Factory模式:专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有共同的父类。
图1:Simple Factory的类图
2、Factory Method模式:将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造函数,究竟应该创建何种对象由具体的子类负责决定。
图2:Factory Method的类图
3、Abstract Factory模式:提供一个共同的接口来创建相互关联的多个对象。
图3:Abstract Factory的类图
三种Factory模式的区别在于:
Simple Factory在于对产品创建过程的简单封装,它简单地根据输入来决定创建何种产品(这些产品不一定属于同一产品族),因此,任何产品种类的更新都将对Simple Factory的代码造成影响;Factory Method面对的是一个产品族,它引入了ConcreteFactory来决定创建产品族中的何种产品,当产品种类增加时,只需创建新的ConcreteFactory来创建新的产品;而Abstract Factory面对的则是多个产品系列,它是Factory Method的延伸,Abstract Factory在一个ConcreteFactory中包含了多个Factory Method,以用于创建多个不同产品族中的多个产品。
需要注意的是,以上所说的产品并非仅限于单个的产品,可以包括一次创建出来的一组相同或者相关产品,从这个意义上讲,三种Factory特别是Factory Method与Abstract Factory之间的界限并非十分明显。
三、应用
三种Factory模式均用于对象的创建,但Factory Method与Abstract Factory由于引进了一个用于定义接口的基类Factory,可以更好地屏蔽内部的创建过程,因此,可以做到客户代码与实现代码的完全隔离(如示例部分的COM类厂);而Simple Factory在这一点上表现则不佳,因此,往往仍然只是客户代码的一部分,只是将对象创建的工作纳入自己管辖之下而已。
但并不是说Abstract Factory就比Simple Factory有用,三种Factory模式的实质是一样的,即用于封装对象的创建过程,在实际使用中应根据自己的需要加以选择。
四、优缺点
优点:由于对对象创建过程的封装,不但使得代码的维护工作变得更加简单(职责分离了,明确了,找问题变得更加容易),同时也使得应用系统对于新增产品变得不那么敏感,当新增产品时,我们只需要修改Factory代码或者扩展新的子类来进行产品的创建,灵活性和可重用性都得到了提高。
缺点:由于职责的分离以及类的数目的增多,代码量的增加往往是不可避免的,同时,随着产品种类的变更,类层次将不可避免发生几何膨胀。
五、示例
1、Simple Factory示例
<Java Design Patterns A Tutorial>一书中还给出了一个很巧妙的Simple Factory的应用实例。该实例如下:
一般的英文人名有两种写法,"firstname lastname"或者"lastname, firstname",现在我们有一个通讯簿程序需要根据输入的人名信息(可能是上面的任意一种形式)分别解析出输入的firstname和lastname,保存到通讯簿文件中。
如何解决这个问题呢?由于问题本身比较简单,我们可以简单地借助if等来解决这一问题。但是,如果我们的需求突然发生变化,我们还要支持一些其它的形式,如firstname abbreviation.,甚至更多的情况,这时我们怎么办呢?如果我们最初采用的是if等判断语句,显然我们需要修改我们解析数据部分的代码,增加更多的分支,所有的代码可能需要重新进行严格的测试。那么,有没有比较OO,比较灵活的解决方案呢?<Java Design Patterns A Tutorial>给了我们如下的解决方案(Java Code):
public class Namer {
//base class extended by two child classes
protected String last; //split name
protected String first; //stored here
public String getFirst() {
return first; //return first name
}
public String getLast() {
return last; //return last name
}
}
public class FirstFirst extends Namer {
//extracts first name from last name
//when separated by a space
public FirstFirst(String s) {
int i = s.lastIndexOf(" "); //find sep space
if (i > 0) {
first = s.substring(0, i).trim();
last = s.substring(i + 1).trim();
}
else {
first = ""; // if no space
last = s; // put all in last name
}
}
}
public class LastFirst extends Namer {
// extracts last name from first name
// when separated by a comma
public LastFirst(String s) {
int i = s.indexOf(","); //find comma
if (i > 0) {
last = s.substring(0, i).trim();
first = s.substring(i + 1).trim();
}
else {
last = s; //if no comma,
first = ""; //put all in last name
}
}
}
public class NamerFactory {
//Factory decides which class to return based on
//presence of a comma
public Namer getNamer(String entry) {
//comma determines name order
int i = entry.indexOf(",");
if (i > 0)
return new LastFirst(entry);
else
return new FirstFirst(entry);
}
}
整个实现方案的类图如下:
图4. Namer, NamerFactory及类图
图中采用的是与图1不同的一种结构,是图1所示的基本的SimpleFactory变形后的结果。当新增需求时,我们要做的是从Namer派生新的输入格式封装类,并对NamerFactory中的代码进行简单修改,根据输入创建新的封装类实例。
2、Factory Method示例
应用Factory Method的最典型的例子当数COM的类厂,以下是一段典型的COM创建并运用组件提供的接口方法的代码:
IUnknown *pUnk=NULL;
IObject *pObject=NULL;
CoInitialize(NULL);
CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);
pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);
pUnk->Release();
pObject->Func();
pObject->Release();
CoUninitialize();
当我们执行上述操作时,类厂在背后默默地为我们完成了创建组件类对象的工作。下面是CoCreateInstance内部实现的伪代码:
CoCreateInstance(....)
{
.......
IClassFactory *pClassFactory=NULL;
CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory);
pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
pClassFactory->Release();
........
}
这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。继续深入一步,看看CoGetClassObject的内部伪码:
CoGetClassObject(.....)
{
//通过查注册表CLSID_Object,得知组件DLL的位置、文件名
//装入DLL库
//使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。
//调用DllGetClassObject
}
DllGetClassObject是干什么的,它是用来获得类厂对象的,只有先得到类厂才能去创建组件。下面是DllGetClassObject的伪码:
DllGetClassObject(...)
{
......
CFactory* pFactory= new CFactory; //类厂对象
//查询IClassFactory指针
pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
pFactory->Release();
......
}
CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
CFactory::CreateInstance(.....)
{
...........
CObject *pObject = new CObject; //组件对象
pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
pObject->Release();
...........
}
以下是上述创建过程的图示:
图5. CoCreateInstance流程图
关于组件创建流程的更为详细的论述,请参见<COM技术内幕>。
如果没有类厂的帮助,而是将组件类的创建工作交由我们自己完成,则我们必须知道组件类的类名,而如果这样,则COM的封装性和可重用性都将大大受损。正是由于运用了Factory Method,COM组件只负责对外提供接口,封装内部实现,包括创建过程的特性才得以完整体现。
在MFC中,CDocTemplate是另一个典型的应用Factory Method的例子,MFC实现中通过派生不同的子类CSingleDocTemplate/CMultiDocTemplate来支持不同的文档类型,感兴趣的朋友可以自己研究CDocTemplate及其子类的相关代码,相关代码在afxwin.h中,与上面讨论的Factory Method不同的是,这里的文档类型(单文档/多文档)并没有严格一致的类与之对应。
以下是一个简单的Factory Method的例子(Java Code):
// Abstract Product Class
abstract class Button {
public String caption;
public abstract void paint();
}
// ConcreteProduct1
class WinButton extends Button {
public void paint() {
System.out.println("I'm a WinButton: " + caption);
}
}
// ConcreteProduct2
class MOTButton extends Button {
public void paint() {
System.out.println("I'm a MOTButton: " + caption);
}
}
// Abstract Factory Class
abstract class GUIFactory {
// Simple Factory Method
public static GUIFactory getFactory(String type) {
if (type.equals("WIN")) {
return(new WinFactory());
}
else if (type.equals("MOT")) {
return(new MOTFactory());
}
else
return null;
}
// factory method
public abstract Button createButton();
}
// ConcreteFactory1
class WinFactory extends GUIFactory {
public Button createButton() {
return(new WinButton());
}
}
// ConcreteFactory2
class MOTFactory extends GUIFactory {
public Button createButton() {
return(new MOTButton());
}
}
class FactoryMethodTest {
public static void main(String[] args) {
if (args.length != 1 || (!args[0].equals("WIN") && !args[0].equals("MOT"))) {
System.out.println("Usage: java FactoryMethodTest [WIN|MOT]");
return;
}
GUIFactory aFactory = GUIFactory.getFactory(args[0]);
Button aButton = aFactory.createButton();
aButton.caption = "Play";
aButton.paint();
}
//output is
//I'm a WinButton: Play
//or
//I'm a MOTButton: Play
}
其中,我们通过从抽象基类GUIFactory派生子类WinFactory/MOTFactory分别创建不同的Button类系中的不同产品WinButton/MOTButton。
3、Abstract Factory示例
前面已经说过,Abstract Factory只是Factory Method的一种扩展,如果将上面例子中抽象工厂类GUIFactory的接口改成:
// Abstract Factory Class
abstract class GUIFactory {
// Simple Factory Method
public static GUIFactory getFactory(String type) {
...
}
public abstract Button createButton();
public abstract Button createPanel();
// more factory methods
}
以对应多个不同的产品类系,并在各子类中实现新增各factory methods,则上面的示例就变成一个Abstract Factory了。
(附注:也许看完了本系列的下一篇:Builder模式,再回过头来看这个例子,你会发现如果再增加一个用于组装的Director类,则上面的例子还可以成为Builder模式的一个应用实例。)