设计模式 - 模板方法

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 };
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、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 };
template2_lib.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();  //pLib为多态指针
16         lib->Run();
17 
18         delete pLib;
19     }
20 }
template2_app.cpp

以上两种情况体现为早晚绑定的关系,情况一,结构化设计,应用程序调用程序库,为早绑定,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/

posted @ 2020-12-28 00:02  朱果果  阅读(99)  评论(0编辑  收藏  举报