模板方法模式是设计模式行为型中最简单的一种设计模式。在实际中你甚至可能经常用到,只是你自己不知道它是一种设计模式罢了。
模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
角色:
抽象类(AbstractClass): 定义抽象的原语操作,具体的子类将重定义它们以实现一个算法,实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义
具体子类 (ConcreteClass): 实现原语操作以完成算法中与特定子类相关的步骤。
UML图:
示例:假如你是一个老师,现在你要给你的学生出一份期末考试试卷。你班上有几十个学生,你将考虑如何为设计考试卷。
经分析显然学生的试卷大部分类容都是一致的,唯一不一致的是姓名和答案。老师设计好试卷,只需要把试卷交个学生填写答案即可。学生不需要把题目照抄一份。
所以我们需要把试卷抽象成基类,并且给学生留下填写答案以及姓名的地方。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class TestPaper { public : void DoTestPaper(){ StudentName(); TestTitleOne(); TestTitleTwo(); }; void TestTitleOne(){ cout<< "题目一:X国的房价会降下来么?" <<endl; AnswerOne(); } void TestTitleTwo(){ cout<< "题目二:说说你的新闻联播的看法?" <<endl; AnswerTwo(); } virtual void AnswerOne() = 0; virtual void AnswerTwo() = 0; virtual void StudentName() = 0; }; |
显然,上面 AnswerOne, AnserTwo,StudentName 就是学生答题的地方,学生不需要把题目也抄下来。只需要实现我们的这三个方法就可以了。
例如:小红的试卷
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class XiaoHongTestPaper : public TestPaper { public : void StudentName(){ cout<< "姓名:小红" <<endl; } void AnswerOne(){ cout<< "答:相信X,相信国家,明年一定降下来。" <<endl<<endl; } void AnswerTwo(){ cout<< "答:新闻联播是我最喜欢的节目啊。" <<endl<<endl; } }; |
小张的试卷:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class XiaoZhangTestPaper : public TestPaper { public : void StudentName(){ cout<< "姓名:小张" <<endl; } void AnswerOne(){ cout<< "答:呵呵,还是去做你的X国梦吧。" <<endl<<endl; } void AnswerTwo(){ cout<< "答:我很幸福" <<endl<<endl; } }; |
客户端:
1
2
3
4
5
6
7
8
9
10
11
|
int main( int argc, char * argv[]) { XiaoHongTestPaper paper1; paper1.DoTestPaper(); XiaoZhangTestPaper paper2; paper2.DoTestPaper(); system ( "pause" ); return 0; } |
关于模板方法的讨论
模板方法模式是很简单模式,但是也应用很广的模式。如上面的分析和实现中阐明的模板方法是采用继承的方式实现算法的异构,其关键点就是将通用算法封装在抽象基类中,并将不同的算法细节放到子类中实现。
模板方法模式获得一种反向控制结构效果,这也是面向对象系统的分析和设计中一个原则 DIP(依赖倒置:Dependency Inversion Principles)。其含义就是父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模块),低层模块反而要依赖高层模块。
继 承 的 强 制 性 约 束 关 系 也 让模板方法模 式 有 不 足 的 地 方 , 我 们 可 以 看 到 对 于ConcreteClass 类中的实现的原语方法 Primitive1(),是不能被别的类复用。假设我们要创建一个 AbstractClass 的变体 AnotherAbstractClass,并且两者只是通用算法不一样,其原语操作想复用 AbstractClass 的子类的实现。但是这是不可能实现的,因为 ConcreteClass 继承自AbstractClass,也就继承了 AbstractClass 的通用算法,AnotherAbstractClass 是复用不了ConcreteClass 的实现,因为后者不是继承自前者。
模板方法模式暴露的问题也正是继承所固有的问题,策略模式则通过组合(委托)来达到和模板方法模式类似的效果,其代价就是空间和时间上的代价,关于策略模式的详细讨论请参考 Strategy 模式解析。