模板方法模式
1.定义
定义一个操作中的算法的框架,而将一些步骤的实现延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
使用模板方法模式制造两款汽车。定义汽车必须有的特质:能够发动,鸣笛和停止,不同型号的汽车实现不同。汽车生产完成后需要对汽车的质量进行检验,测试汽车的所有功能。
抽象汽车模型(抽象汽车类)
public abstract class AbstractCar {
// 汽车可以发动,发动方式不同,需要在实现类里实现
public abstract void start();
// 汽车可以鸣笛,但声音不同,需要在实现类里实现
public abstract void alarm();
// 汽车可以熄火,停止方式也不同,需要在实现类里实现
public abstract void stop();
// 测试汽车的质量,操作步骤一样,先测试启动,再测试喇叭,最后测试熄火
public void run() {
// 发动汽车
this.start();
// 按喇叭
this.alarm();
// 停止汽车
this.stop();
}
}
两种不同型号的汽车(汽车实现类)
public class CarOne extends AbstractCar {
// 发动C1型号的汽车
public abstract void start() {
System.out.println("手摇启动C1型号的汽车");
}
// C1型号的汽车鸣笛
public abstract void alarm() {
System.out.println("C1型号的汽车鸣笛滴滴");
}
// 停止C1型号的汽车
public abstract void stop() {
System.out.println("钥匙关闭C1型号的汽车");
}
}
public class CarTwo extends AbstractCar {
// 发动C2型号的汽车
public abstract void start() {
System.out.println("电子启动C2型号的汽车");
}
// C2型号的汽车鸣笛
public abstract void alarm() {
System.out.println("C1型号的汽车鸣笛哔哔");
}
// 停止C2型号的汽车
public abstract void stop() {
System.out.println("电子关闭C1型号的汽车");
}
}
测试汽车(场景类)
public class Client {
public static void main(String[] args) {
CarOne carOne = new CarOne();
carOne.run();
CarTwo carTwo = new CarTwo();
carTwo.run();
}
}
模板方法模式实现简单,仅仅使用了java的继承机制。以上述例子为例,其中AbstractCar叫做抽象模板,它的方法分为两类:
- 基本方法:由子类实现的方法,并在模板方法中被调用。
- 模板方法:数量不限,一般是一个具体的方法,也就是定义中所说的算法,它实现对基本方法的调度,完成固定的逻辑。
CarOne和CarTwo叫做具体模板,实现抽象模板所定义的基本方法。
注意:抽象模板中的基本方法尽量设计为
protected
类型,不需要i暴露的属性或方法尽量不要设置为protected
类型,实现类非必要的情况,尽量不扩大父类中的访问权限。
2.应用
2.1 优点
- 封装不变部分,扩展可变部分。把不可变部分封装到抽象模板中实现,可变部分通过继承来继续扩展。
- 提取公共的代码,便于后期的代码维护。
- 行为由父类控制,子类只负责实现。
2.2 缺点
- 抽象类定义了部分抽象方法,由子类实现,但是子类的执行结果对父类产生了影响,反过来又影响了父类的执行结果,带来了代码的阅读难度。
2.3 使用场景
- 多个子类有公有的方法,且逻辑基本相同。
- 重要,复杂的算法设计为模板方法,细节功能由子类实现。
- 重构时,相同的代码抽取到父类中,通过钩子方法约束其行为。
3.扩展
在上述造车的基础上实现,C1型车可以控制是否鸣笛,C2型车不鸣笛。
扩展后的汽车抽象模型
public abstract class AbstractCar {
// 汽车可以发动,发动方式不同,需要在实现类里实现
protected abstract void start();
// 汽车可以鸣笛,但声音不同,需要在实现类里实现
protected abstract void alarm();
// 汽车可以熄火,停止方式也不同,需要在实现类里实现
protected abstract void stop();
// 测试汽车的质量,操作步骤一样,先测试启动,再测试喇叭,最后测试熄火
public final void run() {
// 发动汽车
this.start();
if (this.isAlarm()) {
// 按喇叭
this.alarm();
}
// 停止汽车
this.stop();
}
// 钩子方法,默认喇叭会响
protected boolean isAlarm() {
return true;
}
}
扩展后的汽车实现类
public class CarOne extends AbstractCar {
// C1汽车要可以鸣笛
private boolean alarmFlag = true;
// 发动C1型号的汽车
protected abstract void start() {
System.out.println("手摇启动C1型号的汽车");
}
// C1型号的汽车鸣笛
protected abstract void alarm() {
System.out.println("C1型号的汽车鸣笛滴滴");
}
// 停止C1型号的汽车
protected abstract void stop() {
System.out.println("钥匙关闭C1型号的汽车");
}
// 是否鸣笛由驾驶员控制
public void setAlarm(boolean isAlarm) {
this.isAlarm = isAlarm;
}
}
public class CarTwo extends AbstractCar {
// 发动C2型号的汽车
protected abstract void start() {
System.out.println("电子启动C2型号的汽车");
}
// C2型号的汽车鸣笛
protected abstract void alarm() {
System.out.println("C1型号的汽车鸣笛哔哔");
}
// 停止C2型号的汽车
protected abstract void stop() {
System.out.println("电子关闭C1型号的汽车");
}
// C2汽车不能鸣笛
protected boolean isAlarm() {
return false;
}
}
C1汽车由驾驶员控制是否鸣笛,抽象类中的
isAlarm
方法的返回值影响了run
方法的执行结果,即外界条件的改变会影响到模板方法的执行,该方法就叫做钩子方法。