【背景】在软件系统中,经常面临着创建对象的工作。但由于需求的变化,所创建对象的具体类型经常变化。
- 如何应对这种变化?
- 如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
引言
看一个实例,和它在软件工程中的演化过程。原始版本:文件分割器
```cpp class FileSplitter { //文件分割器 public: void split() { //核心行为:分割
} }//...
class MainForm : public Form { TextBox txtFilePath; TextBox txtFileNumber; ProgressBar* progressBar;
public: void Button1_Click() { string filePath = txtFilePath->getText(); int number = atoi(txtFileNumber->getText().c_str());
FileSplitter* splitter = new FileSplitter(filePath, number);
splitter->split();
}
}
这是一个很正常的对象使用模式。它有什么问题呢?单独看是看不出问题的。但在需求变化的角度,就能看出一点蛛丝马迹。
<a name="5W4n6"></a>
#### 迭代一:评估需求变化,并抽象出基类
【评估需求变化】当然你要先评估有没有需求的变化<br />假设目前FileSplitter只支持二进制文件的分割。但是,未来可能会去支持文本文件的分割,图片文件的分割,或者视频文件的分割等等。
【抽象出基类】当你想到这些变化的时候,你就需要抽象出你的基类
```cpp
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 {
//...
void Button1_Click() {
//...
//面向接口编程最直接的表示形式就是:变量要申明为抽象基类
ISplitter* splitter =
new BinarySplitter(); //那new后面怎么办呢?这里还是依赖了具体的类
//MainForm编译时,还需要#include"BinarySplitter.h"
splitter->split();
}
}
迭代二:去除new带来的细节依赖
【思考】ISplitter* splitter = new ISplitter();
这样解决可以吗?
这样解决也不行。ISplitter()
是一个抽象类,是不能被new出来的。
【自定义方法来创建】new可以当成一个方法,但是此时new解决不了我们的问题。那我们是不是可以自定义一个方法?来返回一个对象。而此即是工厂设计模式的一个思想。
//SplitterFactory
class SplitterFactory{ //通过一个工厂类,把new的工作抽象出来,由它负责
public:
ISplitter* CreateSplitter() {
return new BinarySplitter(); //创建Splitter对象
}
}
//MainForm
SplitterFactory factory;
ISplitter* splitter = factory.CreateSplitter();
虽然这样做,很像是把创建的工作抽了出来,很像是有所改善。
但这个问题还没有解决,SplitterFactory
依赖BinarySplitter
,MainForm
依赖SplitterFactory
。因此,最后MainForm也要依赖BinarySplitter
也就是你抽离了一层,还是没有把这个问题完全抽象出来。
【思考】
- 上面是编译式依赖:即在编译的时候,你需要告诉
MainForm
SplitterFactory
是什么;要告诉SplitterFactory
BinarySplitter
是什么,不然编译会通不过,它认为这是一个不完整类型 - C++中什么是运行时依赖呢?答案是virutal虚函数
【虚函数】
- 编译的时候,我们会检查某个类、某个函数的具体实现在哪里(即程序员是否实现了这个类和这个函数)
- 但是虚函数会把检查这个动作延迟,延迟到程序运行的时候(即运行时),才根据具体的情况找这个函数的具体实现在哪里,也就是说:下面这段代码编译是可以通过的。
如此,我们可以用virtual虚函数来解决这个问题。
#include<iostream>
using namespace std;
//抽象类
class ISplitter
{
public:
virtual void split() = 0;
virtual ~ISplitter() {}
};
//工厂基类
class SplitterFactory
{
public:
virtual ISplitter* CreateSplitter()
{
return nullptr;
}
virtual ~SplitterFactory() {}
};
int main()
{
SplitterFactory factory;
ISplitter* splitter = factory.CreateSplitter();
return 0;
}
附:纯需函数的情况也是一样,将检查放到的了运行时
//工厂基类
class SplitterFactory
{
public:
virtual ISplitter* CreateSplitter() = 0; //如果是纯虚函数,编译也可通过。这也证明了虚函数是一种延迟检查的策略
virtual ~SplitterFactory() {}
};
//MainForm中使用
SplitterFactory* factory;
ISplitter* splitter = factory->CreateSplitter();
//SplitterFactory是一个纯虚基类,应该用指针的方式来使用
最终版本:工厂模式
- 抽象类、抽象工厂 ```cpp //抽象类 class ISplitter{ //分割器 public: virtual void split()=0; //接口 virtual ~ISplitter(){} //让子类 };
//工厂基类 class SplitterFactory{ public: virtual ISplitter* CreateSplitter()=0; virtual ~SplitterFactory(){} };
2. 具体类、具体工厂
```cpp
//具体类
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();
}
};
- 应用层:这里要注意,不能写成具体的类,不然还是违背了上面的两种思想。我们应该从外界传进来 ```cpp class MainForm : public Form { SplitterFactory* factory; //只需要一个抽象工厂
public: //通过外界传递进来一个factory //在未来传递一个具体的factory,而不是现在直接指定是哪个具体的factory MainForm(SplitterFactory* factory){ this->factory=factory; }
void Button1_Click(){
ISplitter * splitter = factory->CreateSplitter(); //多态new
splitter->split();
}
};
4. 未来使用
```cpp
#include"PictureSplitter.h" //加入具体的类
SplitterFactory* factory = new PictureSplitterFactory(); //图片工厂
MainForm form(factory);
form.show();
总结
- C++的new没有提供多态的机制,我们可以用虚函数来解决多态创建问题(设计模式是不是也叫“虚函数用法举例”?)
- 通过工厂设计模式,MainForm没有和具体的Splitter产生依赖。将依赖放到了未来,未来的某个文件中是需要依赖具体的Splitter的。
- 设计模式(or面向对象的松耦合设计)实际上呢,并不是把变化消灭掉,并不是把依赖具体类的这个事情消灭掉了,而是把他们赶到一个局部的地方。举个例子:把变化当成一只猫,我们是把猫关到笼子里,而不是让它在你的代码里跳来跳去。
面向接口编程
面向接口编程要求我们,一个对象的类型往往应该申明成抽象类或接口,而不应该申明为具体的类。如果你申明为具体的类,你是没有支持未来的变化的,毕竟你已经把对象的类型定死了。 ```cpp //申明为具体的类:线段Segment Segment seg = new Segment(); //如果之后这个变量要改为Line类型,则需要改为Line line = new Line(); //并且下面使用的方法也需要更改,改动特别大!
//申明为抽象类或接口:抽象类Geometry Geometry geom = new Segment(); //如果之后这个变量要改为Line类型,直接改为Geometry geom = new Line(); //下面的方法就不用改了,可以多态调用
但其实从`Geometry* geom = new Segment()`可以看出,虽然Geometry*是抽象的,但是Segment还是一个具体类型。这其实也解决不了问题,还是需要更改代码。由此工厂模式应运而生。
<a name="zKPYW"></a>
##### 依赖倒置原则
【依赖倒置原则】依赖倒置要求我们应该去依赖抽象,而不应该去依赖实现细节。
1. `ISplitter* splitter`已经变成**抽象依赖**了,已经是编译式依赖
1. 但是`new BinarySplitter()`还是一个**细节依赖**,如此也解决不了问题,也打破了依赖倒置原则
问题所在:`MainForm`在编译的时候,也需要知道`BinarySplitter`存在,才能编译通过。即这还是紧耦合,还是一个编译式的细节依赖,还需要`#include<BinarySplitter.h>`<br />所以,两边都需要改成接口,才能依赖抽象。如此,工厂模式诞生了。解决new带来的细节依赖,避免new过程中导致的紧耦合(依赖具体的类)
【注释】编译式依赖:编译时,再检查是否存在;你存在,我编译才能通过
<a name="rxPUJ"></a>
## 工厂模式
<a name="ABTaT"></a>
#### 模式定义
**定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。——《设计模式》GoF。**<br />目的:解耦;手段:虚函数
1. 创建对象的接口`SplitterFactory::CreateSplitter`
1. 决定创建哪个类的子类:`BinarySplitterFactory`、`TxtSplitterFactory`等
<a name="q5826"></a>
#### 结构
![image.png](https://cdn.nlark.com/yuque/0/2020/png/1465826/1604766079993-630f3ffd-9c90-463c-bfee-bc4f3a16ac2a.png#align=left&display=inline&height=258&margin=%5Bobject%20Object%5D&name=image.png&originHeight=258&originWidth=624&size=58767&status=done&style=none&width=624)
1. `Product`:产品的抽象基类`ISplitter`<br />
1. `ConcreteProduct`:产品的具体基类`BinarySplitter`、`TxtSplitter`等
1. `Creator`:创建者的抽象基类`SplitterFactory`
1. `ConcreteCreator`:创建者的具体类`BinarySplitterFactory`、`TxtSplitterFactory`
红色部分是稳定的。MainForm依赖的就是红色部分<br />蓝色部分是变化的。我们做的事情,就是把蓝色的内容隔离出去,让MainForm不依赖于蓝色部分
<a name="mBkan"></a>
#### 要点总结
1. Factory Method用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
1. Factory Method模式通过面向对象的手法,将所要创建的具体对象工作**延迟**到子类,从而实现一种**扩展(而非更改)**的策略,较好地解决了这种紧耦合关系。
1. Factory Method模式解决“单个对象”的需求变化。缺点在于要求**创建方法/参数相同**
<a name="FfXxr"></a>
## C++完整实例:Geometry相关类
本节来简单介绍工厂模式的完整使用实例,包含工厂模式定义,实现;对象创建与释放;使用等。内容来自GEOS的源码。
<a name="L563k"></a>
#### geometry.h
```cpp
class GeometryFactory; //前置申明
class Geometry{
///Geometry相关的类型
public:
using ConstVect = std::vector<const Geometry*>; //Geometry常量指针的数组
using NonConstVect = std::vector<Geometry*>; //Geometry非常量指针的数组
using Ptr = std::unique_ptr<Geometry> ; //unique_ptr智能指针
///工厂
public:
friend class GeometryFactory; //申明为友元
const GeometryFactory* getFactory() const{
return _factory;
}
private:
const GeometryFactory* _factory;
///构建与析构
public:
Geometry(const Geometry& geom);
Geometry(const GeometryFactory* factory);
virtual std::unique_ptr<Geometry> clone() const = 0; //深拷贝
virtual ~Geometry() {
_factory->dropRef();
}
///分析
public:
//缓冲区操作:此函数内生成的缓冲区,要返回成unique_ptr单一智能指针的形式
//1. 不能返回具体类(如Linearring),会带来细节依赖
//2. 不能返回具体类,就只能返回抽象类的指针
//3. 为防止内存泄漏,用智能指针来管理指针
//4. 选择单一智能指针
std::unique_ptr<Geometry> buffer(double distance) const;
//...
}
geometryfactory.h
//GEOS并没有使用抽象工厂,直接是具体工厂
//所以此工厂依赖了所有产品Geometry、GeometryCollection、MultiPoint等
#include <geos/geom/Geometry.h>
#include <geos/geom/GeometryCollection.h>
#include <geos/geom/MultiPoint.h>
#include <geos/geom/MultiLineString.h>
#include <geos/geom/MultiPolygon.h>
#include <geos/geom/PrecisionModel.h>
//前置申明
class CoordinateSequenceFactory;
class Coordinate;
class CoordinateSequence;
class Envelope;
class Geometry;
class GeometryCollection;
class LineString;
class LinearRing;
class MultiLineString;
class MultiPoint;
class MultiPolygon;
class Polygon;
class GeometryFactory{
//============================引用计数
private:
e
//============================Geometry工厂的创建与析构
private:
bool __autoDestroy;
struct GeometryFactoryDeleter{ //删除者
void operator()(GeometryFactory *p) const{
p->destroy();
}
}
public:
void destroy() {
assert(!_autoDestroy); // don't call me twice !
_autoDestroy = true;
if(! _refCount) {
delete this;
}
}
//GeometryFactory的单一智能指针
//在该智能指针被析构时调用GeometryFactoryDeleter()仿函数
using Ptr = std::unique_ptr<GeometryFactory, GeometryFactoryDeleter>;
static GeometryFactory::Ptr create() {
return GeometryFactory::Ptr(new GeometryFactory());
}
//static GeometryFactory::Ptr create(const PrecisionModel* pm);
//...
//默认工厂
static const GeometryFactory* getDefaultInstance() {
static GeometryFactory defInstance;
return &defInstance;
}
//============================创建相关产品
public:
////创建点
std::unique_ptr<Point> createPoint(std::size_t coordinateDimension = 2) const{ //创建空的点
if (coordinateDimension == 3) {
geos::geom::FixedSizeCoordinateSequence<0> seq(coordinateDimension);
return std::unique_ptr<Point>(createPoint(seq));
}
return std::unique_ptr<Point>(new Point(nullptr, this));
}
Point* createPoint(const Coordinate& coordinate) const {
if(coordinate.isNull()) {
return createPoint().release();
}
else {
return new Point(coordinate, this);
}
}
Point* createPoint(const CoordinateSequence& coordinates) const;
//创建Geometry
std::unique_ptr<Geometry> createEmptyGeometry() const;
}