模板方法模式

超实用的代码复用模式

意图

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 TemplateMethod 使得子类可以不改变一个算法的接口即可重定义该算法的某些特定步骤。

UML 图

uml

  • AbstractClass
    • 定义抽象的操作(PrimitiveOpreation1PrimitiveOpreation12),具体的子类将重定义它们以实现一个算法的各步骤
    • 实现一个模板方法(TemplateMethod),定义一个算法的骨架。该模板方法不仅调用抽象操作,也调用定义在 AbstractClass 或其他对象中的操作
  • ConcreteClass : 实现原语操作以完成算法中与特定子类相关的步骤

看完 UML 图,其实这个设计模式还是很简单的,主要就是尽可能将不变的行为移动到父类中,去除子类中重复的代码

应用场景

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
  • 控制子类扩展,父类可以提供的抽象函数的默认实现方式(不用定义成纯虚函数),子类有选择的重新实现这些虚函数

举个例子

老师出题使用 我一边...一边...造句,然后让同学填空

那么出题的试卷类应该如下:

#ifndef TESTPAPER_H
#define TESTPAPER_H

#include <iostream>
using namespace std;

class TestPaper {
public:
    void makeSentences() {
        string user = this->user();
        string first = this->part1();
        string second = this->part2();
        std::cout << user <<": 我一边" << first << ", 一边" << second << std::endl;
    }

protected:
    virtual string user() {return "参考答案";}
    virtual string part1() { return "认真听课";}
    virtual string part2() { return "仔细做笔记";}
};

#endif // TESTPAPER_H

学生A 的回答如下

#ifndef STUDENTA_H
#define STUDENTA_H

#include "testpaper.h"

class StudentA : public TestPaper {
protected:
    virtual string user() {return "StudentA";}
    virtual string part1() { return "哭";}
    virtual string part2() { return "笑";}
};

#endif // STUDENTA_H

学生B的回答如下:

#ifndef STUDENTB_H
#define STUDENTB_H

#include "testpaper.h"

class StudentB : public TestPaper {
protected:
    virtual string user() {return "StudentB";}
    virtual string part1() { return "睡觉";}
    virtual string part2() { return "起床";}
};

#endif // STUDENTB_H

main 函数中的调用如下:

#include "testpaper.h"
#include "studenta.h"
#include "studentb.h"

int main()
{
    TestPaper paper;
    paper.makeSentences();

    StudentA studenta;
    studenta.makeSentences();

    StudentB studentb;
    studentb.makeSentences();

    return 0;
}

运行结果

参考答案: 我一边认真听课, 一边仔细做笔记
StudentA: 我一边哭, 一边笑
StudentB: 我一边睡觉, 一边起床

注意事项

  • C++ 访问控制
    C++ 中,一个模板方法 (TemplateMethod) 调用的抽象函数 (PrimitiveOpreation) 可以被定义为保护成员,这保证它们只能被模板方法调用,必须 重定义的抽象函数需要定义为 纯虚函数,模板方法自身不需要被重定义,因此可以将模板方法定义为一个非虚成员函数
  • 尽量减少 PrimitiveOpreation
    定义模板方法的一个重要目的是尽量减少一个子类具体实现该算法时必须重定义的那些抽象函数的数目,需要重定义的操作越多,客户程序就越冗长

总结

在继承和多态中,模板方法模式使用的频率还是很高的,很好的遵守了软件开发流行的原则:don't repeat yourself