本篇将会介绍Factory Method工厂方法模式,其属于一个新的类别,将其归结到“对象创建模式”,该模式的简介如下:
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
这四个模式非常接近,解决的是同一个问题,只不过这些问题在演化过程中会有细微的差别,需要四个不同的模式进行应对。
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
对C++设计模式_05_Observer 观察者模式中的文件分割器的代码抽象,将观察者模式等跟本篇介绍内容不相关的去除,只突出跟“对象创建模式”相关的代码。
常规的方法是,创建一个Splitter类,在类中定义一个核心的split方法,在客户端收集参数之后,创建一个对象,通过对象调用方法完成分割。
这种方法存在什么问题呢?
假如我们在一个变化场景看问题,一般意义上,一个类型,需要看到未来变化的需求,这个时候就需要做抽象类或者接口,这是我们最早讲的设计原则-面向接口的编程
。
面向接口的编程告诉我们,以一个对象的类型,往往应该声明为一个抽象类或者接口,而不应该声明为具体的类,一旦声明为具体的类,就意味着没有支持未来的变化。
假设上面的代码中只支持二进制文件的分割,但是未来也可能支持文本文件的分割或者图片文件或视频文件的分割等。那么代码就变成如下所示:
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
class BinarySplitter : public ISplitter{
};
class TxtSplitter: public ISplitter{
};
class PictureSplitter: public ISplitter{
};
class VideoSplitter: public ISplitter{
};
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
//面向接口编程的最基础表现形式,变量声明为抽象基类
ISplitter * splitter=
new BinarySplitter();//依赖具体类
splitter->split();
}
};
上述即为面向接口所编写的程序,之前介绍的模式背后都有一个抽象基类,这是面向对象设计模式的基础。
为什么要实现面向接口编程
设计原则-依赖倒置原则:依赖抽象,不依赖实现细节
ISplitter * splitter=
new BinarySplitter();//依赖具体类
ISplitter * splitter=
是抽象依赖,而new BinarySplitter()
是细节依赖,仍然存在细节依赖也是不可以的,简单来说MainForm 在编译时总体还是依赖BinarySplitter存在才能编译通过的,这就是编译时的细节依赖,这就违背了设计原则中的依赖倒置原则。这个问题如何解决呢?
再回过头看“对象创建模式”模式中提到的“通过“对象创建” 模式绕开new,来避免对象创建(new)”,为什么要避免对象创建(new)的原因也就是上面提到new BinarySplitter()
带来的细节依赖。“它是接口抽象之后的第一步工作”可以理解为它是面向接口编程必然提出的需求,也就是面向接口编程不能只管ISplitter * splitter=
的抽象依赖,而不管new BinarySplitter()
的细节依赖,两边都要变为接口,变为依赖抽象。
想一想在C++中创建对象的方法
由于抽象类是不允许创建对象的,利用new和栈上创建对象的方法替换等号右边,实现右边为抽象的方法也是不可行的。
那么是否可以采用一种方法来返回一个对象,并且利用virtual实现运行时依赖,这就引出本篇的重点Factory Method工厂方法。
//抽象类
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
class MainForm : public Form
{
SplitterFactory* factory;//工厂
public:
MainForm(SplitterFactory* factory){
this->factory=factory;
}
void Button1_Click()
{
//现在的返回值为ISplitter,但是真正创建的对象交给SplitterFactory未来,其中可以放具体的factory,即FileSplitter2.cpp具体工厂内容
ISplitter * splitter=
factory->CreateSplitter(); //多态new
splitter->split();
}
};
其中Button1_Click()
是可以多次点击的,但SplitterFactory* factory;
只需要一个即可(代码如上),MainForm中不需要具体指定具体的工厂,通常通过以下代码外接传递进来的一个具体的factory,例如BinarySplitterFactory
,ISplitter * splitter=factory->CreateSplitter();
创建的就是一个BinarySplitter,这样就可以反复创建BinarySplitter,这个地方的形式我们称为多态new
。
MainForm(SplitterFactory* factory){
this->factory=factory;
}
此时可能会有人想,传进来的具体的factory在其他地方也要创建,也会对具体类产生依赖,这是对的,但是在MainForm没有对具体类,至于MainForm以外的是不归MainForm管的。
面向对象设计模式的松耦合设计很多情况下不是消灭变化(依赖具体类),而是将其赶到局部的地方
。大家可以将变化比作一只猫,将其关到笼子里,而不是让它在你的代码里面跳来跳去。
//具体类
class BinarySplitter : public ISplitter{
};
class TxtSplitter: public ISplitter{
};
class PictureSplitter: public ISplitter{
};
class VideoSplitter: public ISplitter{
};
//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};
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();
}
};
上面代码就是一个完整的Factory Method工厂方法模式。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
——《设计模式》GoF
结合代码来看,“定义一个用于创建对象的接口”指的 就是以下代码,具体即为:virtual ISplitter* CreateSplitter()=0;
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
“让子类决定实例化哪一个类”即为以下代码:
//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};
......
上图是《设计模式》GoF中定义的Factory Method工厂方法的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
延迟
到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。第二条中的扩展
也就是增加对应的factory即可。延迟而非更改
最早的代码中,一旦需求发生变化,就需要在Mainform中更改ISplitter * splitter=new BinarySplitter();//依赖具体类
,现在需求变化之后Mainform中ISplitter * splitter=factory->CreateSplitter();
就不需要改变,只需要增加子类和子类工厂,传给Mainform即可。
C++设计模式——工厂方法模式