【背景】在软件系统中,经常面临着创建对象的工作。但由于需求的变化,所创建对象的具体类型经常变化。

  1. 如何应对这种变化?
  2. 如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

    引言

    看一个实例,和它在软件工程中的演化过程。

    原始版本:文件分割器

    ```cpp class FileSplitter { //文件分割器 public: void split() { //核心行为:分割
    1. //...
    } }

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());

  1. FileSplitter* splitter = new FileSplitter(filePath, number);
  2. splitter->split();
  3. }

}

  1. 这是一个很正常的对象使用模式。它有什么问题呢?单独看是看不出问题的。但在需求变化的角度,就能看出一点蛛丝马迹。
  2. <a name="5W4n6"></a>
  3. #### 迭代一:评估需求变化,并抽象出基类
  4. 【评估需求变化】当然你要先评估有没有需求的变化<br />假设目前FileSplitter只支持二进制文件的分割。但是,未来可能会去支持文本文件的分割,图片文件的分割,或者视频文件的分割等等。
  5. 【抽象出基类】当你想到这些变化的时候,你就需要抽象出你的基类
  6. ```cpp
  7. class ISplitter{ //考虑到未来有其他的分割类,这里抽象出一个分割基类
  8. public:
  9. virtual void split() = 0;
  10. virtual ~ISplitter() {}
  11. //保证资源释放安全性,需要写一个虚析构函数,以便子类能够正常释放
  12. }
  13. class BinarySplitter : public ISplitter{
  14. };
  15. class TxtSplitter: public ISplitter{
  16. };
  17. class PictureSplitter: public ISplitter{
  18. };
  19. class VideoSplitter: public ISplitter{
  20. };
  21. //使用时,即要申明成接口
  22. class MainForm : public Form {
  23. //...
  24. void Button1_Click() {
  25. //...
  26. //面向接口编程最直接的表示形式就是:变量要申明为抽象基类
  27. ISplitter* splitter =
  28. new BinarySplitter(); //那new后面怎么办呢?这里还是依赖了具体的类
  29. //MainForm编译时,还需要#include"BinarySplitter.h"
  30. splitter->split();
  31. }
  32. }

迭代二:去除new带来的细节依赖

【思考】ISplitter* splitter = new ISplitter();这样解决可以吗?
这样解决也不行。ISplitter()是一个抽象类,是不能被new出来的。

【自定义方法来创建】new可以当成一个方法,但是此时new解决不了我们的问题。那我们是不是可以自定义一个方法?来返回一个对象。而此即是工厂设计模式的一个思想。

  1. //SplitterFactory
  2. class SplitterFactory{ //通过一个工厂类,把new的工作抽象出来,由它负责
  3. public:
  4. ISplitter* CreateSplitter() {
  5. return new BinarySplitter(); //创建Splitter对象
  6. }
  7. }
  8. //MainForm
  9. SplitterFactory factory;
  10. ISplitter* splitter = factory.CreateSplitter();

虽然这样做,很像是把创建的工作抽了出来,很像是有所改善。
但这个问题还没有解决,SplitterFactory依赖BinarySplitterMainForm依赖SplitterFactory。因此,最后MainForm也要依赖BinarySplitter
也就是你抽离了一层,还是没有把这个问题完全抽象出来。

【思考】

  1. 上面是编译式依赖:即在编译的时候,你需要告诉MainForm SplitterFactory是什么;要告诉SplitterFactory BinarySplitter是什么,不然编译会通不过,它认为这是一个不完整类型
  2. C++中什么是运行时依赖呢?答案是virutal虚函数

【虚函数】

  1. 编译的时候,我们会检查某个类、某个函数的具体实现在哪里(即程序员是否实现了这个类和这个函数)
  2. 但是虚函数会把检查这个动作延迟,延迟到程序运行的时候(即运行时),才根据具体的情况找这个函数的具体实现在哪里,也就是说:下面这段代码编译是可以通过的。

如此,我们可以用virtual虚函数来解决这个问题。

  1. #include<iostream>
  2. using namespace std;
  3. //抽象类
  4. class ISplitter
  5. {
  6. public:
  7. virtual void split() = 0;
  8. virtual ~ISplitter() {}
  9. };
  10. //工厂基类
  11. class SplitterFactory
  12. {
  13. public:
  14. virtual ISplitter* CreateSplitter()
  15. {
  16. return nullptr;
  17. }
  18. virtual ~SplitterFactory() {}
  19. };
  20. int main()
  21. {
  22. SplitterFactory factory;
  23. ISplitter* splitter = factory.CreateSplitter();
  24. return 0;
  25. }

附:纯需函数的情况也是一样,将检查放到的了运行时

  1. //工厂基类
  2. class SplitterFactory
  3. {
  4. public:
  5. virtual ISplitter* CreateSplitter() = 0; //如果是纯虚函数,编译也可通过。这也证明了虚函数是一种延迟检查的策略
  6. virtual ~SplitterFactory() {}
  7. };
  8. //MainForm中使用
  9. SplitterFactory* factory;
  10. ISplitter* splitter = factory->CreateSplitter();
  11. //SplitterFactory是一个纯虚基类,应该用指针的方式来使用

最终版本:工厂模式

  1. 抽象类、抽象工厂 ```cpp //抽象类 class ISplitter{ //分割器 public: virtual void split()=0; //接口 virtual ~ISplitter(){} //让子类 };

//工厂基类 class SplitterFactory{ public: virtual ISplitter* CreateSplitter()=0; virtual ~SplitterFactory(){} };

  1. 2. 具体类、具体工厂
  2. ```cpp
  3. //具体类
  4. class BinarySplitter : public ISplitter{ //二进制分割器
  5. };
  6. class TxtSplitter: public ISplitter{ //文本分割器
  7. };
  8. class PictureSplitter: public ISplitter{ //图片分割器
  9. };
  10. class VideoSplitter: public ISplitter{ //音频分割器
  11. };
  12. //具体工厂
  13. class BinarySplitterFactory: public SplitterFactory{
  14. public:
  15. virtual ISplitter* CreateSplitter(){
  16. return new BinarySplitter();
  17. }
  18. };
  19. class TxtSplitterFactory: public SplitterFactory{
  20. public:
  21. virtual ISplitter* CreateSplitter(){
  22. return new TxtSplitter();
  23. }
  24. };
  25. class PictureSplitterFactory: public SplitterFactory{
  26. public:
  27. virtual ISplitter* CreateSplitter(){
  28. return new PictureSplitter();
  29. }
  30. };
  31. class VideoSplitterFactory: public SplitterFactory{
  32. public:
  33. virtual ISplitter* CreateSplitter(){
  34. return new VideoSplitter();
  35. }
  36. };
  1. 应用层:这里要注意,不能写成具体的类,不然还是违背了上面的两种思想。我们应该从外界传进来 ```cpp class MainForm : public Form { SplitterFactory* factory; //只需要一个抽象工厂

public: //通过外界传递进来一个factory //在未来传递一个具体的factory,而不是现在直接指定是哪个具体的factory MainForm(SplitterFactory* factory){ this->factory=factory; }

  1. void Button1_Click(){
  2. ISplitter * splitter = factory->CreateSplitter(); //多态new
  3. splitter->split();
  4. }

};

  1. 4. 未来使用
  2. ```cpp
  3. #include"PictureSplitter.h" //加入具体的类
  4. SplitterFactory* factory = new PictureSplitterFactory(); //图片工厂
  5. MainForm form(factory);
  6. form.show();

总结

  1. C++的new没有提供多态的机制,我们可以用虚函数来解决多态创建问题(设计模式是不是也叫“虚函数用法举例”?)
  2. 通过工厂设计模式,MainForm没有和具体的Splitter产生依赖。将依赖放到了未来,未来的某个文件中是需要依赖具体的Splitter的。
  3. 设计模式(or面向对象的松耦合设计)实际上呢,并不是把变化消灭掉,并不是把依赖具体类的这个事情消灭掉了,而是把他们赶到一个局部的地方。举个例子:把变化当成一只猫,我们是把猫关到笼子里,而不是让它在你的代码里跳来跳去。
    面向接口编程
    面向接口编程要求我们,一个对象的类型往往应该申明成抽象类或接口,而不应该申明为具体的类。如果你申明为具体的类,你是没有支持未来的变化的,毕竟你已经把对象的类型定死了。 ```cpp //申明为具体的类:线段Segment Segment seg = new Segment(); //如果之后这个变量要改为Line类型,则需要改为Line line = new Line(); //并且下面使用的方法也需要更改,改动特别大!

//申明为抽象类或接口:抽象类Geometry Geometry geom = new Segment(); //如果之后这个变量要改为Line类型,直接改为Geometry geom = new Line(); //下面的方法就不用改了,可以多态调用

  1. 但其实从`Geometry* geom = new Segment()`可以看出,虽然Geometry*是抽象的,但是Segment还是一个具体类型。这其实也解决不了问题,还是需要更改代码。由此工厂模式应运而生。
  2. <a name="zKPYW"></a>
  3. ##### 依赖倒置原则
  4. 【依赖倒置原则】依赖倒置要求我们应该去依赖抽象,而不应该去依赖实现细节。
  5. 1. `ISplitter* splitter`已经变成**抽象依赖**了,已经是编译式依赖
  6. 1. 但是`new BinarySplitter()`还是一个**细节依赖**,如此也解决不了问题,也打破了依赖倒置原则
  7. 问题所在:`MainForm`在编译的时候,也需要知道`BinarySplitter`存在,才能编译通过。即这还是紧耦合,还是一个编译式的细节依赖,还需要`#include<BinarySplitter.h>`<br />所以,两边都需要改成接口,才能依赖抽象。如此,工厂模式诞生了。解决new带来的细节依赖,避免new过程中导致的紧耦合(依赖具体的类)
  8. 【注释】编译式依赖:编译时,再检查是否存在;你存在,我编译才能通过
  9. <a name="rxPUJ"></a>
  10. ## 工厂模式
  11. <a name="ABTaT"></a>
  12. #### 模式定义
  13. **定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。——《设计模式》GoF。**<br />目的:解耦;手段:虚函数
  14. 1. 创建对象的接口`SplitterFactory::CreateSplitter`
  15. 1. 决定创建哪个类的子类:`BinarySplitterFactory``TxtSplitterFactory`
  16. <a name="q5826"></a>
  17. #### 结构
  18. ![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)
  19. 1. `Product`:产品的抽象基类`ISplitter`<br />
  20. 1. `ConcreteProduct`:产品的具体基类`BinarySplitter``TxtSplitter`
  21. 1. `Creator`:创建者的抽象基类`SplitterFactory`
  22. 1. `ConcreteCreator`:创建者的具体类`BinarySplitterFactory``TxtSplitterFactory`
  23. 红色部分是稳定的。MainForm依赖的就是红色部分<br />蓝色部分是变化的。我们做的事情,就是把蓝色的内容隔离出去,让MainForm不依赖于蓝色部分
  24. <a name="mBkan"></a>
  25. #### 要点总结
  26. 1. Factory Method用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
  27. 1. Factory Method模式通过面向对象的手法,将所要创建的具体对象工作**延迟**到子类,从而实现一种**扩展(而非更改)**的策略,较好地解决了这种紧耦合关系。
  28. 1. Factory Method模式解决“单个对象”的需求变化。缺点在于要求**创建方法/参数相同**
  29. <a name="FfXxr"></a>
  30. ## C++完整实例:Geometry相关类
  31. 本节来简单介绍工厂模式的完整使用实例,包含工厂模式定义,实现;对象创建与释放;使用等。内容来自GEOS的源码。
  32. <a name="L563k"></a>
  33. #### geometry.h
  34. ```cpp
  35. class GeometryFactory; //前置申明
  36. class Geometry{
  37. ///Geometry相关的类型
  38. public:
  39. using ConstVect = std::vector<const Geometry*>; //Geometry常量指针的数组
  40. using NonConstVect = std::vector<Geometry*>; //Geometry非常量指针的数组
  41. using Ptr = std::unique_ptr<Geometry> ; //unique_ptr智能指针
  42. ///工厂
  43. public:
  44. friend class GeometryFactory; //申明为友元
  45. const GeometryFactory* getFactory() const{
  46. return _factory;
  47. }
  48. private:
  49. const GeometryFactory* _factory;
  50. ///构建与析构
  51. public:
  52. Geometry(const Geometry& geom);
  53. Geometry(const GeometryFactory* factory);
  54. virtual std::unique_ptr<Geometry> clone() const = 0; //深拷贝
  55. virtual ~Geometry() {
  56. _factory->dropRef();
  57. }
  58. ///分析
  59. public:
  60. //缓冲区操作:此函数内生成的缓冲区,要返回成unique_ptr单一智能指针的形式
  61. //1. 不能返回具体类(如Linearring),会带来细节依赖
  62. //2. 不能返回具体类,就只能返回抽象类的指针
  63. //3. 为防止内存泄漏,用智能指针来管理指针
  64. //4. 选择单一智能指针
  65. std::unique_ptr<Geometry> buffer(double distance) const;
  66. //...
  67. }

geometryfactory.h

  1. //GEOS并没有使用抽象工厂,直接是具体工厂
  2. //所以此工厂依赖了所有产品Geometry、GeometryCollection、MultiPoint等
  3. #include <geos/geom/Geometry.h>
  4. #include <geos/geom/GeometryCollection.h>
  5. #include <geos/geom/MultiPoint.h>
  6. #include <geos/geom/MultiLineString.h>
  7. #include <geos/geom/MultiPolygon.h>
  8. #include <geos/geom/PrecisionModel.h>
  9. //前置申明
  10. class CoordinateSequenceFactory;
  11. class Coordinate;
  12. class CoordinateSequence;
  13. class Envelope;
  14. class Geometry;
  15. class GeometryCollection;
  16. class LineString;
  17. class LinearRing;
  18. class MultiLineString;
  19. class MultiPoint;
  20. class MultiPolygon;
  21. class Polygon;
  22. class GeometryFactory{
  23. //============================引用计数
  24. private:
  25. e
  26. //============================Geometry工厂的创建与析构
  27. private:
  28. bool __autoDestroy;
  29. struct GeometryFactoryDeleter{ //删除者
  30. void operator()(GeometryFactory *p) const{
  31. p->destroy();
  32. }
  33. }
  34. public:
  35. void destroy() {
  36. assert(!_autoDestroy); // don't call me twice !
  37. _autoDestroy = true;
  38. if(! _refCount) {
  39. delete this;
  40. }
  41. }
  42. //GeometryFactory的单一智能指针
  43. //在该智能指针被析构时调用GeometryFactoryDeleter()仿函数
  44. using Ptr = std::unique_ptr<GeometryFactory, GeometryFactoryDeleter>;
  45. static GeometryFactory::Ptr create() {
  46. return GeometryFactory::Ptr(new GeometryFactory());
  47. }
  48. //static GeometryFactory::Ptr create(const PrecisionModel* pm);
  49. //...
  50. //默认工厂
  51. static const GeometryFactory* getDefaultInstance() {
  52. static GeometryFactory defInstance;
  53. return &defInstance;
  54. }
  55. //============================创建相关产品
  56. public:
  57. ////创建点
  58. std::unique_ptr<Point> createPoint(std::size_t coordinateDimension = 2) const{ //创建空的点
  59. if (coordinateDimension == 3) {
  60. geos::geom::FixedSizeCoordinateSequence<0> seq(coordinateDimension);
  61. return std::unique_ptr<Point>(createPoint(seq));
  62. }
  63. return std::unique_ptr<Point>(new Point(nullptr, this));
  64. }
  65. Point* createPoint(const Coordinate& coordinate) const {
  66. if(coordinate.isNull()) {
  67. return createPoint().release();
  68. }
  69. else {
  70. return new Point(coordinate, this);
  71. }
  72. }
  73. Point* createPoint(const CoordinateSequence& coordinates) const;
  74. //创建Geometry
  75. std::unique_ptr<Geometry> createEmptyGeometry() const;
  76. }