一、动机
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
二、模式定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
Factory Method使得一个类的实例化延迟到子类。
——《设计模式》GoF
三、例子
同样是文件分割的类,基类为纯虚函数,四个继承于基类的类对应于分割不同类型的文件。
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
class TxtSplitter: public ISplitter{
public:
virtual voidsplit()
{
}
};
class PictureSplitter: public ISplitter{
public:
virtual voidsplit()
{
/*******/
}
};
class VideoSplitter: public ISplitter{
public:
virtual voidsplit()
{
/*******/
}
};
好了,上面是我们写好的接口,在实现具体的业务时,会根据情况创建不同的类对象:
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click()
{
ISplitter * splitter= new PictureSplitter();//依赖具体类
splitter->split();
}
void Button2_Click()
{
ISplitter * splitter= new VideoSplitter();//依赖具体类
splitter->split();
}
};
可以看到我们已经实现了使用一个基类指针指向一个具体的子类对象,实现了部分多态,但没有解耦。
问题:这时只能根据按钮来区分需要创建什么类对象,或者能够传入一个参数来区分不同什么类型文件,再使用switch
区分去创建对象,但这样也同样的耦合。
四、改进
改进思路:将创建具体类对象的实现封装为多态。
同样这部分是不变的
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
class TxtSplitter: public ISplitter{
public:
virtual voidsplit()
{
}
};
class PictureSplitter: public ISplitter{
public:
virtual voidsplit()
{
/*******/
}
};
class VideoSplitter: public ISplitter{
public:
virtual voidsplit()
{
/*******/
}
};
然后创建一个工厂的基类:
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
具体工厂类继承自工厂基类,对应于上面的三种文件类型的类对象,每个类的函数返回一个类对象。
注意:我们可以发现,返回的是基类,但函数返回值类型是基类指针,还是使用的基类指针指向的子类对象。
//具体工厂
class TxtSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
};
class PictureSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter();
}
};
class VideoSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter();
}
};
下面就要进行解耦了,在框架类使用工厂基类指针调用创建方法返回具体对象。
class MainForm : public Form
{
SplitterFactory* factory;//工厂
public:
MainForm(SplitterFactory* factory){//构造函数中传入具体工厂指针,含义见下个代码段
this->factory=factory;
}
void Button1_Click(){
ISplitter * splitter = factory->CreateSplitter(); //多态new
splitter->split();
}
};
/* 使用时 */
VideoSplitterFactory videoFactoryObj;
MainForm *mainFormObj = new MainForm(&videoFactoryObj);
mainFormObj->Button_Click();
看了这块以后,我发现在这个类中定义一个基类工厂指针,再进行赋值似乎很多余,比如下面这样实现不行吗?
class MainForm : public Form
{
public:
void Button1_Click(SplitterFactory *factory){
ISplitter * splitter = factory->CreateSplitter(); //多态new
splitter->split();
}
};
这样似乎很好,看起来也更清爽,但对于调用该接口的就麻烦了一步。具体的工厂传参应该在一个使用关注不到的函数(构造函数)去实现,调用接口的角度来看不应该去关注这个参数。
那么你可能会说,就是在构造函数中也需要多传了一个参数呀,但那是构造函数所关心的。
五、总结
- Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。
- Factor Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好的解决了这种紧耦合关系。
- Factor Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
多说一点:
其实是可以在第一种方法的框架类定义一个文件分割的基类指针,构造函数中传入文件分割的继承类对象,不过这就不是工厂方法了,而是我们之前说过的模板方法。
类似的可以使用表驱动的方式去解决这种问题。事件映射函数指针的方式.