查看原文
其他

从制造汽车过程学习什么是模板方法模式。

倪升武 武哥聊编程 2022-08-24


这篇文章主要带大家学习一下模板方法模式。我们先来思考一个问题:假如现在老板让你做一个汽车的模型,要求只要完成基本功能即可,不考虑扩展性。如果是你,你会怎么做呢?


1. 制造悍马汽车


正常情况下,我们首先都会根据经验设计一个类图。



由上面这个类图可知,非常简单的实现了悍马车,该车有两个型号H1和H2。那现在我们开始实现这两个型号的悍马车,思路是什么呢?


首先我们得把抽象类写好,然后两个不同的模型实现类通过简单的继承就可以实现要求。来看看抽象类的代码


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
public class HummerH1 implements HummerModel {

 @Override
 public void start() {
   System.out.println("H1发动……");
 }

 @Override
 public void stop() {
   System.out.println("H1停止……");
 }

 @Override
 public void alarm() {
   System.out.println("H1鸣笛……");
 }

 @Override
 public void engineBoom() {
   System.out.println("H1轰鸣……");
 }

 @Override
 public void run() {
   this.start();
   this.engineBoom();
   this.alarm();
   this.stop();
 }
}

//悍马H2
public class HummerH2 implements HummerModel {

    @Override
    public void start() {
        System.out.println("H2发动……");
    }

    @Override
    public void stop() {
        System.out.println("H2停止……");
    }

    @Override
    public void alarm() {
        System.out.println("H2鸣笛……");
    }

    @Override
    public void engineBoom() {
        System.out.println("H2轰鸣……");
    }

    @Override
    public void run() {
        this.start();
        this.engineBoom();
        this.alarm();
        this.stop();
    }
}


读者看到这里,肯定已经感觉到这代码写的有点问题了,没错,两个悍马的run方法完全相同。所以这个run方法应该出现在抽象类中,不应该在实现类中,抽象是所有子类的共性封装。所以我们需要修改一下抽象类。


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();
 }
}


这样两个实现类就不用实现run方法了,可以直接拿来用。其实,这就是模板方法模式,这个run方法就是模板方法。


2. 模板方法模式的抽象


模板方法模式很简单,它主要是定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。就像上面悍马车一样,跑的方法都一样,但是具体细节可能不同。


模板方法模式的通用类图如下。



模板方法模式确实很简单,仅仅使用了Java的继承机制就能实现,但是它是一个使用非常广泛的设计模式。其中AbstractClass叫做抽象模板,它的方法分为两类:基本方法(由子类去实现)和模板方法(可以有一个或多个,也就是一个框架,实现对基本方法的调度,完成固定的逻辑)。


为了防止恶意的操作,一般模板方法上都添加上final关键字,不允许被覆写。我们来看一下AbstractClass模板。


public abstract class AbstractClass {
 //基本方法
 protected abstract void doSomething();
 protected abstract void doAnything();
 //模板方法
 public void templateMethod() {
   //调用基本方法,完成相关的逻辑
   this.doAnything();
   this.doSomething();
 }
}


具体实现就不写了,已经很明了了。


3. 模板方法模式的扩展


我们还是继续来扯一扯上面那个悍马的例子,现在老板说这车干嘛跑起来就要鸣笛,太吵了,难道不是应该让用户决定它是否要鸣笛么?


虽然老板平时一般都比较讨厌,但是这次他说的好像有点道理……那好办,我们可以修改一下抽象模板类中的方法。


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();
 }
 //我们加了一个判断方法,默认返回true
 protected boolean isAlarm() {
   return true;
 }
}


可以看出,我们在模板类中增加了一个判断方法来判断是否要鸣笛,那现在就好办了,具体实现类只要重写这个方法就可以做到人为控制是否要鸣笛了,下面我们来看一下实现类。


public class HummerH1 extends HummerModel {
  //判断标记
 private boolean alarmFlag = true;
 @Override
 public void start() {
   System.out.println("H1发动……");
 }

 @Override
 public void stop() {
   System.out.println("H1停止……");
 }

 @Override
 public void alarm() {
   System.out.println("H1鸣笛……");
 }

 @Override
 public void engineBoom() {
   System.out.println("H1轰鸣……");
 }
 
 // 覆写isAlarm方法,返回判断标记
 @Override
 protected boolean isAlarm() {
   return this.alarmFlag;
 }
 
 // 设置判断标记
 public void setAlarm(boolean isAlarm) {
   this.alarmFlag = isAlarm;
 }
}


这个实现很好,我们在实现类中定义一个判断标记,然后对外提供一个public接口setAlarm来让外界设置这个判断标记,这就像是开关一样,想让它ture和false都行。这个isAlarm方法俗称钩子方法有了钩子方法的模板方法模式才算完美,大家可以想象一下,由子类的一个方法返回值决定公共部分的执行结果,这个是很有吸引力的。我们来测试一下。


public class Test {
 public static void main(String[] args) throws IOException {
   System.out.println("----H1型悍马----");
   System.out.println("是否需要喇叭声响? 0-不需要  1-需要");
   String type = new BufferedReader(new InputStreamReader(System.in)).readLine();
   HummerH1 h1 = new HummerH1();
   if(type.equals("0")) {
     h1.setAlarm(false);
   }
   h1.run();
 }
}


当输入不同的指令后,就会决定不同的动作:即要不要鸣笛,至此,这个模板方法模式就介绍完了。本文参考自《设计模式之禅》,这本书不错,感兴趣的朋友可以看一看。


END


往期精彩:

漫画 | 趣说单例模式

女娲造人的时候就已经有了工厂模式!

同样是程序员,为什么别人比你更优秀?


关注我们

每天进步一点点


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存