zoukankan      html  css  js  c++  java
  • [设计模式] 设计模式课程(二)-- 模板模式

    概述

    • 按目的划分属于行为型模式,按封装类型划分属于组件协作类模式
    • 动机/场景:软件构建过程中,对某项任务常常有稳定的整体操作结构,但各个子步骤有很多改变的需求,或者由于固有原因(如框架与应用之间的关系)而无法和任务的整体结构同时实现
    • 需求:如何在确定稳定操作的前提下,灵活应对各个子步骤的变化或晚期实现需求?
    • 背景:现代软件分工后第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合
    • 在超类中定义一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤

    场景

    • 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 }
    View Code

    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 };
    View Code
    • 程序库开发人员实现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 };
    View Code

    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 }
    View Code
    • 程序库开发人员实现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         // 怪物不会建造单位。
    View Code

    示例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 }
    View Code

    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!

    参考

    https://refactoringguru.cn/design-patterns/template-method 

  • 相关阅读:
    getopt for windows
    开源代码学习之Tinyhttpd
    GCC 中的编译器堆栈保护技术
    读《程序员的思维修炼》有感
    main之前初始化流程
    平均速度
    显示图案
    圆的面积和周长
    C#(Winform) Http 发送数据
    Android BaseAdapter的使用
  • 原文地址:https://www.cnblogs.com/cxc1357/p/12271922.html
Copyright © 2011-2022 走看看