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方法了,可以直接拿来用。其实,这就是模板方法模式。
2. 模板方法模式的定义
模板方法模式很简单,它的定义是:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses define certain steps of an algorithm without changing the algorithm's structure.
即定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可冲定义该算法的某些特定步骤。模板方法模式的通用类图如下:
模板方法模式确实很简单,仅仅使用了Java的继承机制,但是它是一个应用非常广泛的模式,其中AbstractClass叫做抽象模板,它的方法分为两类:
1) 基本方法(由子类去实现)
2) 模板方法(可以有一个或多个,也就是一个框架,实现对基本方法的调度,完成固定的逻辑)
为了防止恶意的操作,一般模板方法上都添加上final关键字,不允许被覆写。
我们来看一下AbstractClass模板:
public abstract class AbstractClass {
// 基本方法
protected abstract void doSomething();
protected abstract void doAnything();
// 模板方法
public void templateMethod() {
// 调用基本方法,完成相关的逻辑
this.doAnything();
this.doSomething();
}
}
具体实现类就不写了……
3. 模板方法模式的优缺点
优点:
1)封装不变部分,扩展可变部分:把认为不变部分的算法封装到父类实现,可变部分则可以通过继承来实现,很容易扩展。
2)提取公共部分代码,便于维护:上面悍马的例子就是个很好的解释。
3)行为由父类控制,由子类实现。
缺点:
模板方法模式颠倒了我们平常的设计习惯:抽象类负责声明最抽象、最一般的事物属性和方法,实现类实现具体的事物属性和方法。在复杂的项目中可能会带来代码阅读的难度。
4. 模板方法模式的扩展
还是上面那个悍马的例子,现在老板说这车干嘛跑起来就要鸣笛,太吵了,难道不是应该让用户决定它是否要鸣笛么?好像确实是这样的……那好办,我们可以修改一下抽象模板类中的方法:
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() { // 我们加了一个判断方法,默认返回true
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轰鸣……");
}
@Override
protected boolean isAlarm() { // 覆写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();
}
}
当输入不同的指令后,就会决定不同的动作:即要不要鸣笛,至此,这个模板方法模式就介绍完了。
参考推荐: