模板方法模式可以说是所有设计模式中最简单的,得到了广泛的应用。本文的大部分内容参考了课程https://www.bilibili.com/video/av22292899中的相关内容,在此表示感谢,也再次向大家推荐这门课。
1、模式定义
模板方法模式就是,先定义一个操作中算法的骨架,从而将一些步骤延迟到子类来实现,这使得子类可以不改变一个算法的结构即可重定义(override,重写)该算法的某些特定步骤。
2、为什么使用模板方法模式?
在软件构建的过程中,对于某一项任务,它常常具有稳定的整体结构(整个工作流程,比如先做什么,再做什么,往往是稳定,不太经常更改的),但各个子步骤却会有很多改变的需求。有时候因为一些固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。那如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化,或晚期实现需求?这时候就用到了模板方法模式的思想。
3、模板方法模式与传统的软件实现思路比较
(1)传统的软件实现思路
假设我们需要完成一项任务,在任务中,需要一系列的流程Step1()到Step5()。其中Step1()、Step3()、Step5()在类Library中定义,是类库开发人员编写的。Step2()、Step4()在类Application中定义,是开发具体应用程序的开发人员编写的。开发具体应用程序的开发人员在main函数中,确定整个流程的步骤,通过实例化两个类Library和Application,调用它们的方法来实现整个程序的流程。
//程序库开发人员
//Library.cpp
class Library {
public:
void Step1() {
//...
}
void Step3() {
//...
}
void Step5() {
//...
}
};
//应用程序开发人员
//Application.cpp
class Application {
public:
bool Step2() {
//...
}
void Step4() {
//...
}
};
//应用程序开发人员
//main.cpp
int main()
{
Library lib();
Application app();
lib.Step1();
if (app.Step2()) {
lib.Step3();
}
for (int i = 0; i < 4; i++) {
app.Step4();
}
lib.Step5();
return 0;
}
实际上,很多任务的流程往往都是固定的,并不需要应用程序开发人员来确定,而是由框架开发人员来编写。此时这个流程就可以更早地在Library类中确定。
(2)用模板方法模式进行改进
Step1()、Step3()、Step5()是稳定的,在编写Library类的时候就已经可以确定了,所以就在Library类中直接实现。而Step2()和Step4()是不确定的,可能会变化,框架开发人员现在还不能确定它应该怎么写,于是就定义为纯虚函数,其具体实现在子类Application中,交给应用程序开发人员编写(它们就被称为模板方法)。
//Library.cpp
//程序库开发人员定下操作流程的框架以及一开始就可以确定的一些步骤
class Library {
public:
void Run() {
Step1();
if (Step2()) {
//Step2()为虚函数,在子类Application中有具体实现
Step3();
}
for (int i = 0; i < 4; i++) {
Step4();//Step4()为虚函数,在子类Application中有具体实现
}
Step5();
}
virtual ~Library() {}//基类的析构函数必须写成虚的
protected:
void Step1() {//稳定
//...
}
void Step3() {//稳定
//...
}
void Step5() {//稳定
//...
}
virtual bool Step2() = 0;//可能会变化的方法,声明为纯虚函数,让子类重写(override)
virtual bool Step4() = 0;//可能会变化的方法,声明为纯虚函数,让子类重写(override)
}
这样,应用程序开发人员负责框架中Step2和Step4的具体实现,不必理会整个操作的流程。
//应用程序开发人员
//Application.cpp
class Application :public Library{
public:
virtual bool Step2() override final {
//子类重写实现
//...
}
virtual void Step4() override final {
//子类重写实现
//...
}
virtual ~Application() {
//...
}
};
int main()
{
//创建Application对象
//注意这是个多态指针,声明类型是Library
//实际类型是Application
//调用虚函数的时候,根据虚函数的动态绑定规则调用子类方法
Library *plib=new Application();
plib->Run();
delete plib;
return 0;
}
4、总结
优点:减轻Application开发人员负担,Application开发人员只需要负责维护模板方法的具体实现即可,不需要考虑设计整个程序的框架流程(当然,为了更好地实现这些方法,有必要去了解框架流程)。
模板方法模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。可以这么说,几乎所有的面向对象程序设计都会有意无意地用到这个方法。
在具体实现方面,被模板方法调用的虚方法在父类可以有具体的实现,也可以没有任何实现(纯虚方法),但一般推荐把它们声明为protected。而且一般来说推荐在子类对模板方法进行重写时加上final关键字,以免不小心被子类的子类重写。