设计模式 - 模板方法
GOF - 23 模式分类
从目的来看:
- 创建型:将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象创建时具体类型实现引来的冲击;
- 结构型:通过类继承或者对象组合获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击;
- 行为型:通过类继承或者对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击;
从范围来看:
- 类模式处理类与子类的静态关系。
- 对象模式处理对象间的动态关系。
从封装变化角度对模式分类
重构获得模式
- 面向对象设计模式是“好的面向对象设计”,所谓好是指可 “应对变化,提高复用”;
- 现代软件设计的特征:需求的频繁变化。设计模式的要点就是“寻找变化点”,然后在变化处应用设计模式,从而更好应对需求变化。“什么时候,什么地点应用设计模式”比“理解设计模式结构本身”更为重要。
- 设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。
重构关键技法
- 静态 —> 动态
- 早绑定 ——> 晚绑定
- 继承 ——> 组合
- 编译时依赖 ——> 运行时依赖
- 紧耦合 ——> 松耦合
“组件协作”模式
- 现代软件专业分工之后的第一个结果是“框架与应用程序的划 分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之 间的松耦合,是二者之间协作时常用的模式。
典型模式
• Template Method
• Observer / Event
• Strategy
Template Method 模式
动机(Motivation)
- 在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
- 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
结构化软件设计流程
第一种情况,库开发人员开发1、3、5三个步骤,应用程序开发人员开发2、4步骤及主流程(程序框架),示例如下:
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 };
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 }
面向对象软件设计流程
第二种情况,程序库开发人员开发1、3、5以及程序框架(稳定),应用程序开发人员开发2、4。步骤2、4易发生变化,程序库开发人员没法决定其内容,所以先声明2、4为纯虚函数,由继承的子类来重写2、4方法。
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 //基类的虚构函数要声明为虚函数的原因 21 //在实现多态时,当基类操作派生类,在析构时防止只析构基类而不析构派生类 22 virtual ~Library(){ } 23 24 protected: 25 26 void Step1() { //稳定 27 //..... 28 } 29 void Step3() {//稳定 30 //..... 31 } 32 void Step5() { //稳定 33 //..... 34 } 35 36 virtual bool Step2() = 0;//变化 37 virtual void Step4() =0; //变化 38 };
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(); //pLib为多态指针 16 lib->Run(); 17 18 delete pLib; 19 } 20 }
以上两种情况体现为早晚绑定的关系,情况一,结构化设计,应用程序调用程序库,为早绑定,C语言主要都是这种设计;
情况二,面向对象设计,Libray虽然也写的早,但是会调用写得晚的应用程序的内容,即晚绑定。
模式定义
- 定义一个操作中的算法的骨架 (稳定) ,而将一些步骤延迟 (变化) 到子类中。 Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的 某些特定步骤。 ——《 设计模式》 GoF
Template Method即模板方法,也就是第二情况的设计,算法的结构即Run()方法,可重定义的特定步骤即2、4步骤。
在Library中,稳定的算法Run()写成非虚函数,支持变化的部分写成虚函数;
注意:
设计模式最大的作用就是在稳定与变化之间找到隔离点,然后分离它们,进而管理变化。对于极端的情况,软件所有部分都在变化,那么就所有设计模式都失效,若所有部分都是稳定的,设计模式也没存在的意义。所以在模式应用时,要分辨出软件结构中哪些是相对稳定的,变化的
结构
要点总结
- Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性) 为很多应用程序框架提供了灵活的扩展点(继承+多态),是代码复用方面的基本实现结构。
- 除了可以灵活应对子步骤的变化外, “不要调用我,让我来调用你” 的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
参考:https://github.com/liu-jianhao/Cpp-Design-Patterns/