概述
- 按目的划分属于行为型模式,按封装类型划分属于组件协作类模式
- 动机/场景:软件构建过程中,对某项任务常常有稳定的整体操作结构,但各个子步骤有很多改变的需求,或者由于固有原因(如框架与应用之间的关系)而无法和任务的整体结构同时实现
- 需求:如何在确定稳定操作的前提下,灵活应对各个子步骤的变化或晚期实现需求?
- 背景:现代软件分工后第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合
- 在超类中定义一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤
场景
- Spirng 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等
- 在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异
联系
- 工厂方法模式是模板方法模式的一种特殊形式,工厂方法可以作为一个大型模板方法中的一个步骤
- 模板方法基于继承机制,它允许你通过扩展子类中的部分内容来改变算法;策略模式基于组合机制,可通过对相应行为提供不同策略来改变对象的行为。模板方法在类层次上运作,是静态的;策略模式在对象层次上运作,是动态的(允许运行时切换行为)
细节
- Run()是一个模板(稳定),但稳定中有变化,变化的部分写成虚函数
- 基类的虚函数写成虚函数,以调用子类的虚构函数
- 原代码是早期软件开发(结构化程序设计)经常采用的方式,重构后是现代软件开发(面向对象程序设计)经常采用的方式
- 定义一个操作中的算法的稳定骨架,而将一些变化步骤延迟到子类中,使得子类可以复用一个算法的结构或重写该算法的某些特定步骤
- 采用简洁的机制(虚函数的多态性),为应用程序框架提供灵活的扩展点,使代码复用得以实现
- 缺点:假定了Run()是稳定的
- 如果一个程序是完全稳定或完全变化的,则任何设计模式都没有作用
- 设计模式的关键:在变化和稳定之间寻找隔离点,分离他们,从而管理变化
- 把变化像兔子一样关在笼子里,不让它污染整个房间
- 反向控制(IOC):“不要调用我,让我来调用你”
- UML图:红色是稳定部分,蓝色是变化部分
- 建议把虚函数声明为protected
- 其他晚绑定机制:C中的函数指针(虚函数背后也是函数指针)
- 为防止恶意操作,一般模板方法都加上 final 关键词
结构
- 抽象类:声明作为算法步骤的方法,以及依次调用他们的实际模板方法,算法步骤可以被声明为抽象类型
- 具体类:可以重写所有步骤,但不能重写模板方法自身
示例1
template1_lib.cpp
1 //应用程序开发人员 2 class Application{ 3 public: 4 bool Step2(){ 5 //... 6 } 7 8 void Step4(){ 9 //... 10 } 11 }; 12 13 int main() 14 { 15 Library lib(); 16 Application app(); 17 18 lib.Step1(); 19 20 if (app.Step2()){ 21 lib.Step3(); 22 } 23 24 for (int i = 0; i < 4; i++){ 25 app.Step4(); 26 } 27 28 lib.Step5(); 29 30 }
template1_app.cpp
1 //程序库开发人员 2 class Library{ 3 4 public: 5 void Step1(){ 6 //... 7 } 8 9 void Step3(){ 10 //... 11 } 12 13 void Step5(){ 14 //... 15 } 16 };
- 程序库开发人员实现Step1(),Step3(),Step5()
- 应用开发人员实现Step2(),Step4()及应用程序主流程
- 早绑定:应用调用库(库写的早,应用写的晚)
- 用了面向对象语言,但设计思维是结构化的
- 早期C语言开发常用流程,应用开发人员要自己写主流程
template2_lib.cpp
1 //程序库开发人员 2 class Library{ 3 public: 4 //稳定 template method 5 void Run(){ 6 7 Step1(); 8 9 if (Step2()) { //支持变化 ==> 虚函数的多态调用 10 Step3(); 11 } 12 13 for (int i = 0; i < 4; i++){ 14 Step4(); //支持变化 ==> 虚函数的多态调用 15 } 16 17 Step5(); 18 19 } 20 virtual ~Library(){ } 21 22 protected: 23 24 void Step1() { //稳定 25 //..... 26 } 27 void Step3() {//稳定 28 //..... 29 } 30 void Step5() { //稳定 31 //..... 32 } 33 34 virtual bool Step2() = 0;//变化 35 virtual void Step4() =0; //变化 36 };
template2_app.cpp
1 //应用程序开发人员 2 class Application : public Library { 3 protected: 4 virtual bool Step2(){ 5 //... 子类重写实现 6 } 7 8 virtual void Step4() { 9 //... 子类重写实现 10 } 11 }; 12 13 int main() 14 { 15 Library* pLib=new Application(); 16 lib->Run(); 17 18 delete pLib; 19 } 20 }
- 程序库开发人员实现Step1(),Step3(),Step5()及应用程序主流程,预留Step2(),Step4()给应用开发人员实现
- 应用开发人员实现Step2(),Step4()
- 多态指针(声明类型和实际类型不一致,按虚函数的动态绑定规则调用)
- 基类的虚构函数写成虚函数
- 晚绑定:库调用应用(框架开发指导思想)
- 稳定中有变化,稳定的代码写成普通函数,变化的写成虚函数
- 面向对象时代,绝大多数软件框架都有模板,应用开发人员不用写主流程(好处在于省事,弊端在于无法看清全局)
示例2
1 // 抽象类定义了一个模板方法,其中通常会包含某个由抽象原语操作调用组成的算 2 // 法框架。具体子类会实现这些操作,但是不会对模板方法做出修改。 3 class GameAI is 4 // 模板方法定义了某个算法的框架。 5 method turn() is 6 collectResources() 7 buildStructures() 8 buildUnits() 9 attack() 10 11 // 某些步骤可在基类中直接实现。 12 method collectResources() is 13 foreach (s in this.builtStructures) do 14 s.collect() 15 16 // 某些可定义为抽象类型。 17 abstract method buildStructures() 18 abstract method buildUnits() 19 20 // 一个类可包含多个模板方法。 21 method attack() is 22 enemy = closestEnemy() 23 if (enemy == null) 24 sendScouts(map.center) 25 else 26 sendWarriors(enemy.position) 27 28 abstract method sendScouts(position) 29 abstract method sendWarriors(position) 30 31 // 具体类必须实现基类中的所有抽象操作,但是它们不能重写模板方法自身。 32 class OrcsAI extends GameAI is 33 method buildStructures() is 34 if (there are some resources) then 35 // 建造农场,接着是谷仓,然后是要塞。 36 37 method buildUnits() is 38 if (there are plenty of resources) then 39 if (there are no scouts) 40 // 建造苦工,将其加入侦查编组。 41 else 42 // 建造兽族步兵,将其加入战士编组。 43 44 // ... 45 46 method sendScouts(position) is 47 if (scouts.length > 0) then 48 // 将侦查编组送到指定位置。 49 50 method sendWarriors(position) is 51 if (warriors.length > 5) then 52 // 将战斗编组送到指定位置。 53 54 // 子类可以重写部分默认的操作。 55 class MonstersAI extends GameAI is 56 method collectResources() is 57 // 怪物不会采集资源。 58 59 method buildStructures() is 60 // 怪物不会建造建筑。 61 62 method buildUnits() is 63 // 怪物不会建造单位。
示例3
1 public abstract class Game { 2 abstract void initialize(); 3 abstract void startPlay(); 4 abstract void endPlay(); 5 6 public final void play() { 7 initialize(); 8 startPlay(); 9 endPlay(); 10 } 11 } 12 13 public class Cricket extends Game{ 14 15 @Override 16 void initialize() { 17 System.out.println("Cricket Game Initialized! Start playing."); 18 } 19 20 @Override 21 void startPlay() { 22 System.out.println("Cricket Game Started. Enjoy the game!"); 23 } 24 25 @Override 26 void endPlay() { 27 System.out.println("Cricket Game Finished!"); 28 } 29 } 30 public class Football extends Game { 31 32 @Override 33 void endPlay() { 34 System.out.println("Football Game Finished!"); 35 } 36 37 @Override 38 void initialize() { 39 System.out.println("Football Game Initialized! Start playing."); 40 } 41 42 @Override 43 void startPlay() { 44 System.out.println("Football Game Started. Enjoy the game!"); 45 } 46 } 47 48 public class TemplatePatternDemo { 49 50 public static void main(String[] args) { 51 Game game = new Cricket(); 52 game.play(); 53 System.out.println(); 54 game = new Football(); 55 game.play(); 56 } 57 }
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!
参考