模板方法模式 Template Method Pattern

238 阅读3分钟

模板方法模式 Template Method Pattern

定义

定义一个算法的骨架, 而将一些步骤延迟到子类中.

看不懂没关系因为我也没有读懂这句话, 这句话反正是大家都这么说

Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

案例

背景

某个动物园, 有一套固定的表演流程, 但是其中有若干个表演子流程可创新替换, 以尝试迭代更新表演流程

分析

需求强调了: 是有一套固定的流程, 所以我们要考虑接口隔离原则, 不要让用户去选择他们不需要的接口

public:
    void Show() {
        if (Show0())
            PlayGame(); // 里氏替换
        Show1();
        Show2();
        Show3();
    }

// 接口隔离 不要让用户去选择它们不需要的接口
private:
    void PlayGame() {
        std::cout << "after Show0, then play game" << std::endl;
    }

private:
    bool Show0() {
        std::cout << _type << " show0" << std::endl;
        return true;
    }

    void Show1() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 3) {
            
        }
    }

    void Show2() {
        if (_type == 20) {
            
        }
        std::cout << "base Show2" << std::endl;
    }

    void Show3() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        }
    }

然后我们通过type来做迭代

public:
    ZooShow(int type = 1) : _type(type) {}
private:
    bool Show0() {
        std::cout << _type << " show0" << std::endl;
        return true;
    }

    void Show1() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 3) {
            
        }
    }

    void Show2() {
        if (_type == 20) {
            
        }
        std::cout << "base Show2" << std::endl;
    }

    void Show3() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        }
    }

不同的type代表不同的版本

这么做的问题

不满足单一原则和开闭原则

不满足单一原则: 把所有功能都放在了一起, 随着产品迭代, type越来越多, 整个代码会变得非常的臃肿, 而且非常不适合扩展, 如果以后有个需求是查找某一个type的流程, 会变得异常复杂

不满足开闭原则:

public:
    ZooShow(int type = 1) : _type(type) {}

这种通过传参的方式, 不断修改类, 造成了类的职责不断的膨胀

优化

我们应该去扩展

对这个案例来说, 变化点是不同的Show方式, Show0, Show1, Show2

所以把Show0,1,2改成虚函数

// 接口隔离 不要让用户去选择它们不需要的接口
private:
    void PlayGame() {
        std::cout << "after Show0, then play game" << std::endl;
    }

// 
protected:
    virtual bool Show0() {
        std::cout << _type << " show0" << std::endl;
        return true;
    }

    virtual void Show1() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 3) {
            
        }
    }

    virtual void Show2() {
        if (_type == 20) {
            
        }
        std::cout << "base Show2" << std::endl;
    }

    virtual void Show3() {
        if (_type == 1) {
            std::cout << _type << " Show1" << std::endl;
        } else if (_type == 2) {
            std::cout << _type << " Show1" << std::endl;
        }
    }

然后再通过子类继承去扩展

class ZooShowEx1 : public ZooShow {
protected:
    virtual bool Show0(){
        cout << "show1" << endl;
        return true;
    }
    virtual void Show2(){
        cout << "show3" << endl;
    }
};

class ZooShowEx2 : public ZooShow {
protected:
    virtual void Show1(){
        cout << "show1" << endl;
    }
    virtual void Show2(){
        cout << "show3" << endl;
    }
};

class ZooShowEx3 : public ZooShow {
protected:
    virtual void Show1(){
        cout << "show1" << endl;
    }
    virtual void Show3(){
        cout << "show3" << endl;
    }
    virtual void Show4() {
        //
    }
};

使用的时候也非常的简单

ZooShow *zs = new ZooShowEx3;
// ZooShow *zs1 = new ZooShowEx1;
// ZooShow *zs2 = new ZooShowEx2;
zs->Show();

这里有个特殊的, Show0有个PlayGame

注意虚函数

要点

  • 子类复写父类子流程, 使得父类的骨架流程丰富
  • 方向控制流程的典型应用
  • 父类protected保护子类要复写的子流程; 这样子类的子流程只能父类来调用

想想框架的使用, 框架已经定义出来一套固定的流程, 用户去调用框架的时候就是定制了子流程, 这就是反向控制的思想

案例2

再讲一个例子但是这个例子就不用很多代码了, 在逛google的时候看到了一个更好的例子

假如你正在开发一款分析公司文档的数据挖掘程序, 用户需要向程序输入各种格式(PDF,DOC,CSV), 程序则会试图从这些文件中抽取有意义的数据, 并以统一的格式将其返回给用户。

该程序的首个版本仅支持 DOC 文件, 在接下来的一个版本中, 程序能够支持 CSV 文件, 一个月后,程序能够支持从 PDF 文件中抽取数据

一段时间后, 你发现这三个类中包含许多相似代码, 尽管这些类处理不同数据格式的代码完全不同, 但数据处理和分析的代码却几乎完全一样. 如果能在保持算法结构完整的情况下去除重复代码, 不是更好吗

file = openFile(path)
rawData = extractData(file) // doc, pdf, csv
data = parseData(rawData) // doc, pdf, csv
analysis = analyzeData(data)
sendReport(analysis)
closeFile(file)

只有提取文本和分析的时候是不一样的其他地方都是一样的这就是个算法骨架, 我们只要想办法让提取文本和解析数据解耦即可

还有另一个与使用这些类的客户端代码相关的问题: 客户端代码中包含许多条件语句, 以根据不同的处理对象类型选择合适的处理过程; 如果所有处理数据的类都拥有相同的接口或基类, 那么你就可以去除客户端代码中的条件语句, 转而使用多态机制来在处理对象上调用函数

模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在模板方法中依次调用这些方法, 步骤可以是 抽象的, 也可以有一些默认的实现

为了能够使用算法, 客户端需要自行提供子类并实现所有的抽象步骤

让我们考虑如何在数据挖掘应用中实现上述方案, 我们可为三个解析算法创建一个基类, 该类将定义调用了一系列不同文档处理步骤的模板方法。

image.png

总结

就是通过固定算法来约束子类的行为

其实不是什么特别难理解的一个设计模式, 要抓住父类有固定的骨架这一个特点, 要记得先要保证设计原则再去思考设计模式

资料: