模板方法模式

 

  模板方法 模式就是在模板方法中按照一个的规则和顺序调用基本方法。

  现准备做一批悍马车辆模型……
  设计类图如下: 

    模板方法模式

   非常简单的实现,先写个抽象类,然后两个不同型号的模型实现类.

  HummerModel抽象类的程序清单如下:
  //Hummer Model是悍马车辆模型的意思,不是悍马美女车模
  public abstract class HummerModel {
    public abstract void start();      //首先,这个模型要能够被发动起来
    public abstract void stop();       //能发动,那还要能停下来,那才是真本事
    public abstract void alarm();     //喇叭会出声音,是滴滴叫,还是哔哔叫
    public abstract void engineBoom();   //引擎会轰隆隆的响,不响那是假的
    public abstract void run();        //那模型应该会跑吧
  }

  H1型号悍马的定义如下:
  //悍马车是每个越野者的最爱,其中H1最接近军用系列
  public class HummerH1Model extends HummerModel {
    public void start() {
      System.out.println("悍马H1发动...");
    }
    public void stop() {
      System.out.println("悍马H1停车...");
    }
    public void alarm() {
      System.out.println("悍马H1鸣笛...");
    }
    public void engineBoom() {
      System.out.println("悍马H1引擎声音是这样在...");
    }
    //这个方法是很有意思的,它要跑,那肯定要启动,停止了等,也就是要调其他方法  
    public void run() {
      this.start();       //先发动汽车
      this.engineBoom();   //引擎开始轰鸣
      this.alarm();         //跑的过程中遇到一条狗挡路,就按喇叭
      this.stop();       //到达目的地就停车
    }
  }

  然后看悍马H2型号的实现:
  //H1和H2有什么差别,还真不知道,真没接触过悍马
  public class HummerH2Model extends HummerModel {
    public void alarm() {
      System.out.println("悍马H2鸣笛...");
    }
    public void engineBoom() {
      System.out.println("悍马H2引擎声音是这样在...");
    }
    public void start() {
      System.out.println("悍马H2发动...");
    }
    public void stop() {
      System.out.println("悍马H1停车...");
    }

    //H2要跑,那肯定要启动,停止了等,也就是要调其他方法
    public void run() {
      this.start();        //先发动汽车
      this.engineBoom();    //引擎开始轰鸣
      this.alarm();        //跑的过程中遇到一条狗挡路,就按喇叭
      this.stop();        //到达目的地就停车
    }
  }

   然后程序写到这里,你就看到问题了,run方法的实现应该在抽象类上,不应该在实现类上,好,我们修改一下类图和实现: 

    模板方法模式

  把run方法放到了抽象类中,那代码也相应的改变一下,先看HummerModel.java:
  //Hummer Model是悍马车辆模型的意思,不是悍马美女车模
  public abstract class HummerModel {
    public abstract void start();       //首先,这个模型要能够被发动起来
    public abstract void stop();       //能发动,那还要能停下来,那才是真本事
    public abstract void alarm();       //喇叭会出声音,是滴滴叫,还是哔哔叫
    public abstract void engineBoom();   //引擎会轰隆隆的响,不响那是假的

    //那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑
    public void run() {
      this.start();             //先发动汽车
      this.engineBoom();         //引擎开始轰鸣
      this.alarm();             //跑的过程中遇到一条狗挡路,就按喇叭
      this.stop();             //到达目的地就停车
    }
  }

  下面是HummerH1Model.java程序清单:
  //悍马车是每个越野者的最爱,其中H1最接近军用系列
  public class HummerH1Model extends HummerModel {
    public void alarm() {
      System.out.println("悍马H1鸣笛...");
    }

    public void engineBoom() {
      System.out.println("悍马H1引擎声音是这样在...");
    }

    public void start() {
      System.out.println("悍马H1发动...");
    }

    public void stop() {
      System.out.println("悍马H1停车...");
    }
  }

  下面是HummerH2Model.java的程序清单:
  //H1和H2有什么差别,还真不知道,真没接触过悍马
  public class HummerH2Model extends HummerModel {
    public void alarm() {
      System.out.println("悍马H2鸣笛...");
    }

    public void engineBoom() {
      System.out.println("悍马H2引擎声音是这样在...");
    }

    public void start() {
      System.out.println("悍马H2发动...");
    }

    public void stop() {
      System.out.println("悍马H2停车...");
    }
  }

  类图修改完毕了,程序也该好了,提交给老大,老大一看,挺好,就开始生产了,并提交给客户使用了,那客户是如何使用的呢?类图上增加一个Client类,就是客户,我们这个是用main函数来代替他使用,类图如下: 

  模板方法模式

  然后看增加的Client.java程序,非常的简单:
  //客户开始使用这个模型
  public class Client {
    public static void main(String[] args) {
      //客户开着H1型号,出去遛弯了
      HummerModel h1 = new HummerH1Model();
      h1.run();      //汽车跑起来了;

      //客户开H2型号,出去玩耍了
      HummerModel h2 = new HummerH2Model();
      h2.run();
    }
  }

  运行的结果如下: 

  模板方法模式

  

  非常非常的简单,那如果我告诉这就是模板方法模式你会不会很不屑呢?就这模式,太简单了,我一直在使用呀,是的,你经常在使用,但你不知道这是模板方法模式,那些所谓的高手就可以很牛X的说“用模板方法模式就可以实现…”,你还要很崇拜的看着,哇,牛人,模板方法模式是什么呀

  然后我们回顾我们模型,回头一想,不对呀,需求分析的有点问题,客户要关心模型的启动,停止,鸣笛,引擎声音吗?他只要在run的过程中,听到或看都成了呀,暴露那么多的方法干啥?好了,我们重新修改一下类图: 

  模板方法模式

  把抽象类上的四个方法设置为protected访问权限,好了,既然客户不关心这几个方法,而且这四个方法都是由子类来实现的,那就设置成protected模式。咦~,那还有个缺陷,run方法既然子类都不修改,那是不是可以设置成final类型呢?是滴是滴,类图如下: 

  模板方法模式

  好了,这才是模板方法模式,就是这个样子,我们只要修改抽象类代码就可以了。
  HummerModel.java程序清单如下:
  public abstract class HummerModel {
    protected abstract void start();       //首先,这个模型要能够被发动起来
    protected abstract void stop();        //能发动,那还要能停下来,那才是真本事
    protected abstract void alarm();        //喇叭会出声音,是滴滴叫,还是哔哔叫
    protected abstract void engineBoom();    //引擎会轰隆隆的响,不响那是假的

    //那模型应该会跑吧
    final public void run() {
      this.start();         //先发动汽车
      this.engineBoom();     //引擎开始轰鸣
      this.alarm();         //跑的过程中遇到一条狗挡路,就按喇叭
      this.stop();         //到达目的地就停车
    }
  }

  其他的子类都不用修改(如果要修改,就是把四个方法的访问权限由public修改protected),

  大家请看这个run方法,他定义了调用其他方法的顺序,并且子类是不能修改的,这个叫做模板方法;
  start、stop、alarm、engineBoom这四个方法是子类必须实现的,而且这四个方法的修改对应了不同的类,这个叫做基本方法,基本方法又分为三种:在抽象类中实现了的基本方法叫做具体方法;在抽象类中没有实现,在子类中实现了叫做抽象方法,我们这四个基本方法都是抽象方法,由子类来实现的;还有一种叫做钩子方法,这个等会讲。

  到目前为止,这两个模型都稳定的运行,突然有一天,老大又找到了我,“客户提出新要求了,那个喇叭想让它响就响,你看你设计的模型,车子一启动,喇叭就狂响,赶快修改一下”,确实是设计缺陷,呵呵,不过是我故意的,那我们怎么修改呢?看修改后的类图:

  模板方法模式

  增加一个方法,isAlarm(),喇嘛要不要响,这就是钩子方法(Hook Method),那我们只要修改一下抽象类就可以了:
  public abstract class HummerModel {
    protected abstract void start();        //首先,这个模型要能够被发动起来
    protected abstract void stop();         //能发动,那还要能停下来,那才是真本事
    protected abstract void alarm();       //喇叭会出声音,是滴滴叫,还是哔哔叫
    protected abstract void engineBoom();     //引擎会轰隆隆的响,不响那是假的

    //那模型应该会跑吧
    final public void run() {
      this.start();               //先发动汽车
      this.engineBoom();           //引擎开始轰鸣

      if(this.isAlarm()){             //喇嘛想让它响就响,不想让它响就不响
        this.alarm();
      }

      this.stop();               //到达目的地就停车
    }

    protected boolean isAlarm(){
      return true;               //钩子方法,默认喇叭是会响的
    }
  }

  钩子方法模式是由抽象类来实现的,子类可以重写的,H2型号的悍马是不会叫的,喇叭是个摆设,看HummerH2Model.java代码:
  //H1和H2有什么差别,还真不知道,真没接触过悍马
  public class HummerH2Model extends HummerModel {
    protected void alarm() {
      System.out.println("悍马H2鸣笛...");
    }
    protected void engineBoom() {
      System.out.println("悍马H2引擎声音是这样在...");
    }
    protected void start() {
      System.out.println("悍马H2发动...");
    }
    protected void stop() {
      System.out.println("悍马H1停车...");
    }
    protected boolean isAlarm() {
      return false;               //默认没有喇叭的
    }
  }

  那H2型号的模型都没有喇叭,就是按了喇叭也没有声音,那客户端这边的调用没有任何修改,出来的结果就不同,我们先看Client.java程序:
  //客户开始使用这个模型
  public class Client {
    public static void main(String[] args) {
      HummerH2Model h2 = new HummerH2Model();
      h2.run();        //H2型号的悍马跑起来
    }
  }

  运行的出来的结果是这样的:
  那H1又有所不同了,它的喇叭要不要响是由客户来决定,其实在类图上已经标明了setAlarm这个方法,我们看HummerH1Model.java的代码:
  public class HummerH1Model extends HummerModel {
    private boolean alarmFlag = true;    //是否要响喇叭
    protected void start() {
      System.out.println("悍马H1发动...");
    }
    protected void stop() {
      System.out.println("悍马H1停车...");
    }
    protected void alarm() {
      System.out.println("悍马H1鸣笛...");
    }
    protected void engineBoom() {
      System.out.println("悍马H1引擎声音是这样在...");
    }

    protected boolean isAlarm() {
      return this.alarmFlag;
    }
    //要不要响喇叭,是有客户的来决定的
    public void setAlarm(boolean isAlarm){
      this.alarmFlag = isAlarm;
    }
  }
  这段代码呢修改了两个地方,一是重写了父类的isAlarm()方法,一是增加了一个setAlarm方法,由调用者去决定是否要这个功能,也就是喇叭要不要滴滴答答的响,哈哈,那我们看看Client.java的修改:
  //客户开始使用这个模型
  public class Client {
    public static void main(String[] args) {
      //客户开着H1型号,出去遛弯了
      HummerH1Model h1 = new HummerH1Model();
      h1.setAlarm(true);
      h1.run();        //汽车跑起来了;
    }
  }
  运行的结果如下:

  模板方法模式

  看到没,这个模型run起来就有声音了,那当然把h1.setAlarm(false)运行起来喇叭就没有声音了,钩子方法的作用就是这样滴。 

   那我们总结一下模板方法模:

  模板方法模式就是在模板方法中按照一个的规则和顺序调用基本方法,具体到我们上面那个例子就是run方法按照规定的顺序(先调用start,然后再调用engineBoom,再调用alarm,最后调用stop)调用本类的其他方法,并且由isAlarm方法的返回值确定run中的执行顺序变更,通用类图如下: 

    模板方法模式

 

  其中TemplateMethod就是模板方法,operation1和operation2就是基本方法,模板方法是通过汇总或排序基本方法而产生的结果集。模板方法在一些开源框架中应用很多,它提供了一个抽象类,然后开源框架写了一堆子类,在《XXX In Action》中就说明了,如果你需要扩展功能,可以继承了这个抽象类,然后修改protected方法,再然后就是调用一个类似execute方法,就完成你的扩展开发,确实是一种简单的模式。
  初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”,这个问题很有普遍性,反正我是被问过好几回,那么父类是否可以调用子类的方法呢?我的回答是能,但强烈的、极度的不建议,怎么做呢?

  把子类传递到父类的有参构造中,然后调用;
  使用反射的方式调用,你使用了反射还有谁不能调用的?!
  父类调用子类的静态方法。
  这三种都是父类直接调用子类的方法,好用不?好用!解决问题了吗?解决了!项目中允许使用不?不允许!我就一直没有搞懂为什么要父类调用子类的方法,如果一定要调用子类,那为什么要继承它呢?
  搞不懂。其实这个问题可以换个角度去理解,在重写了父类部分方法后,再调用从父类继承的方法,产生不同的结果(而这正是模板方法模式),这是不是也可以理解为父类调用了子类的方法呢?

  你修改了子类,影响了父类的结果,模板方法模式就是这样效果。