【背景】在软件系统中,经常面临着创建对象的工作。但由于需求的变化,所创建对象的具体类型经常变化。
- 如何应对这种变化?
- 如何绕过常规的对象创建方法(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只支持二进制文件的分割。但是,未来可能会去支持文本文件的分割,图片文件的分割,或者视频文件的分割等等。【抽象出基类】当你想到这些变化的时候,你就需要抽象出你的基类```cppclass 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解决不了我们的问题。那我们是不是可以自定义一个方法?来返回一个对象。而此即是工厂设计模式的一个思想。
//SplitterFactoryclass SplitterFactory{ //通过一个工厂类,把new的工作抽象出来,由它负责public:ISplitter* CreateSplitter() {return new BinarySplitter(); //创建Splitter对象}}//MainFormSplitterFactory factory;ISplitter* splitter = factory.CreateSplitter();
虽然这样做,很像是把创建的工作抽了出来,很像是有所改善。
但这个问题还没有解决,SplitterFactory依赖BinarySplitter,MainForm依赖SplitterFactory。因此,最后MainForm也要依赖BinarySplitter
也就是你抽离了一层,还是没有把这个问题完全抽象出来。
【思考】
- 上面是编译式依赖:即在编译的时候,你需要告诉
MainFormSplitterFactory是什么;要告诉SplitterFactoryBinarySplitter是什么,不然编译会通不过,它认为这是一个不完整类型 - 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(); //多态newsplitter->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>#### 结构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```cppclass 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;//创建Geometrystd::unique_ptr<Geometry> createEmptyGeometry() const;}
