将4年前的设计模式笔记再看一遍(6),Factory Method

74 阅读3分钟

一、分类描述

通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持创建的稳定。它是接口抽象后的第一步工作。

二、模式定义

工厂模式,是除了单例之外,听过最多的一个设计模式。

软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

1、原始版本

最开始,只有一个文件分割器,但是还会出现许多其他的分割器。

class FileSplitter {
public:
    void split() {
        // ...
    }
};

当有变化的时候,便需要抽象出基类。

class ISplitter {
public:
    virtual void split() 0;
    virtual ~ISplitter() {}
};

// 具体类
class BinarySplitter : public ISplitter {};

class TxtSplitter : public ISplitter {};

class PictureSplitter : public ISplitter {};

class VedioSplitter : public ISplitter {};

// 使用场景
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());
        
        // 此处ISplitter面向接口编程,但是编译的时候,还是要依赖具体(BinarySplitter)类
        ISplitter *splitter = new BinarySplitter(filePath, number);
        
        splitter.spilt();
    }
};

2、初次优化

添加一个Factory类。

class SplitterFactory {
public:
    ISplitter* createSplitter() {
        return new BinarySplitter();
    }
};

// 使用场景
class MainForm : public Form {
public:
    void Button1_Click(){
        // 先把参数干掉
        SplitterFactory factory;
        ISplitter *splitter = factory.createSplitter();
        
        splitter.spilt();
    }
};

3、再次优化

对于这个示例,我理解每一步是如何变化的。虚函数,延迟到运行时才知道具体调用哪个子类的方法。

// Factory也改为virtual
class SplitterFactory {
public:
    virtual ISplitter* createSplitter() 0;
    virtual ~SplitterFactory() {}
};

// 具体工厂
class BinarySplitterFactorypublic SplitterFactory {
public:
    virtual ISplitter* createSplitter() {
        return new BinarySplitter();
    }
};

class TxtSplitterFactorypublic SplitterFactory {
public:
    virtual ISplitter* createSplitter() {
        return new TxtSplitter();
    }
};

class PictureSplitterFactorypublic SplitterFactory {
public:
    virtual ISplitter* createSplitter() {
        return new PictureSplitter();
    }
};

class VedioSplitterFactorypublic SplitterFactory {
public:
    virtual ISplitter* createSplitter() {
        return new VedioSplitter();
    }
};

// 使用场景
class MainForm : public Form {
    SplitterFactory* factory;
    
public:
    // 这里传递factory进来,为什么不直接传递ISplitter*呢?
    // 重新看了一遍视频,再次思考,这里确实是可以直接传递ISplitter的,也没有依赖BinarySplitter。
    // 所以应该是这个示例,不太合适?
    // 四年后再看一遍这视频,依然没想到答案但有一个猜想方向:
    // 此处添加的Factory层,是为将Splitter的创建更延迟些
    MainForm(SplitterFactory* factory){
        this->factory = factory;
    }

    void Button1_Click(){
        ISplitter *splitter = factory->createSplitter(); // 多态new
        
        splitter.spilt();
    }
};

三、各种tip

1、面向接口编程,最简单的体现为,变量要申明为抽象基类。为什么要实现面向接口编程?依赖倒置,应该依赖抽象,不应该依赖实现细节。

2、C++创建对象的方法

BinarySplitter bs(); // 在栈上创建
ISplitter *splitter = new BinarySplitter(); // 在堆中创建
// 最后一种就是上面的Factory设定

3、工厂模式并不是把变化干掉,只是将变化放到一个位置,把猫放进笼子里。(其实整体来讲,这个模式想要达到的目标,依然是为了让代码修改起来更容易些。)

4、老师的原话,理解工厂模式后,其他的模式,很快就理解了?

5、看了Java的实现,发现比c++的简单一些。中间的差异是?c++多了一层对splitter的虚函数封装?Java这边的实现有点类似key-value方式,扩展只需要加key和value,并不影响原有流程。

6、关于虚函数,接口类的析构函数必须为虚函数,这基于内存原因,只有当析构函数为虚函数时,子类的析构函数才会被调用到;为多态而添加的虚函数,是一种延迟,延迟到运行时。

7、四年后,老师说首先应该知道问题,然后再去解决问题,如果不清楚问题而直接学设计模式,是会蒙掉的,现在的我认为极有道理,于是再做一次记录:设计模式最好的学习时机,可能是在做完一个完整项目后。