定义一个操作中的算法的骨架,而将步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
扩展:run()方法可以按照规定的顺序执行,也可以由isAlarm()方法的返回值影响run()中的执行顺序。
模板模式通过定义模板方法抽象类(包含节点方法和执行流程方法两部分),由具体类继承后实现各节点方法,最后在主函数中,调用具体类对象从模板类中继承来的执行流程方法。—— 可用在抽象类中定义一套组合拳模板,便于主体调用。
一、制造悍马
1、模式中的角色
抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法
(基本方法:由子类实现的方法,并且在模板方法被调用)
(模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现 对基本方法的调度,完成固定的逻辑)
2、抽象悍马模型
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();
}
}
3、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引擎声音");
}
}
4、H2悍马模型
public class HummerH2Model extends HummerModel{
public void start() {
System.out.println("悍马H2发动...");
}
public void stop() {
System.out.println("悍马H2停止");
}
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
public void engineBoom() {
System.out.println("悍马H2引擎声音");
}
}
5、场景类
public class Client {
public static void main(String[] args) {
HummerModel h1 = new HummerH1Model();
h1.run();
}
}
二、模板方法模式的定义
1、 抽象模板类
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//抽象方法
public void templateMethod(){
/*
* 调用基本方法,完成相关的逻辑
*/
this.doAnything();
this.doSomething();
}
}
2、 具体模板类
public class ConcreteClass1 extends AbstractClass{
protected void doSomething() {
//业务逻辑处理
}
protected void doAnything() {
//业务逻辑处理
}
}
&
public class ConcreteClass2 extends AbstractClass{
protected void doSomething() {
//业务逻辑处理
}
protected void doAnything() {
//业务逻辑处理
}
}
3、 场景类
public class ClientModel {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
class1.templateMethod();
class2.templateMethod();
}
}
注意:抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需暴露的属性或方法尽量不要设置为protected类型,实现类若非必要,尽量不要扩大父类中的访问权限。
三、模板方法模式的应用
1. 优点
封装不变部分,扩展可变部分
把认为不变部分的算法封闭到父类实现,而可变部分的则可以通过继承来继续扩展。
提取公共部分代码,便于维护
把公共代码
封闭到父类实现
行为由父类控制,子类实现
2. 缺点
抽象类一般只用于写抽象方法,而该模式的抽象方法中有部分实现方法,另一部分抽象方法由子类实现,子类的结果影响父类,在复杂的项目中会带来代码阅读的难度。
3. 使用场景
3. 使用场景
多个子类有公有的方法,并且逻辑基本相同时。
重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见扩展)约束其行为
四、 模板方法模式的扩展
1、 扩展后的抽象模板类
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();
if(this.isAlarm()){
this.alarm();
}
this.stop();
}
//钩子方法,默认喇叭是会响的
protected Boolean isAlarm(){
return true;
}
}
2、 扩展后的H1悍马
public class HummerH1Model extends HummerModel{
private boolean alarmFlag = true; //要响喇叭
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引擎声音");
}
protected Boolean isAlarm() {
return alarmFlag;
}
//要不要喇叭由用户来决定。
public void setAlarm(Boolean isAlarm){
this.alarmFlag = isAlarm;
}
}
3、扩展后的H2悍马
public class HummerH2Model extends HummerModel{
public void start() {
System.out.println("悍马H2发动...");
}
public void stop() {
System.out.println("悍马H2停止");
}
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
public void engineBoom() {
System.out.println("悍马H2引擎声音");
}
//默认没有喇叭的
protected Boolean isAlarm() {
return false;
}
}
4、场景类
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("--------------H1型号---------------");
System.out.println("H1型号要不要喇叭?0-不要,1要");
String type = (new BufferedReader(new InputStreamReader(System.in))).readLine();
HummerH1Model h1 = new HummerH1Model();
if(type.equals("0")){
h1.setAlarm(false);
}
h1.run();
System.out.println("--------------H2型号-----------------");
HummerModel h2 = new HummerH2Model();
h2.run();
}
}
扩展后H1型号的悍马由客户自己控制,也就是说外界条件改变,影响到模板方法的执行。在我们的
抽象类中isAlarm的返回值就是影响了模板方法的执行结果,该方法叫“钩子方法”。
run()方法可以按照规定的顺序执行,也可以由isAlarm()方法的返回值影响run()中的执行顺序。五、最佳实践
父类如何调用子类方法?
1. 反射
2. 调用静态方法
3. 子类重写父类方法后,调用父类方法就相当于调用子类方法