模板方法模式的实现要素
-
抽象基类
- 提供基本的实现方法,具有共性的方法
- 抽象方法,不知道具体实现的方法,由子类进行具体实现
- 可选钩子函数,基类中提供默认或空实现
- Template方法(final),将所有方法汇总而成的模板方法
好莱坞原则:子类可以替换掉父类可变逻辑,但不能改变整体逻辑
-
具体子类
- 实现基类中的抽象方法,提供具体的,个性化的实现
- 可选的覆盖钩子函数,个性化的影响局部行为
-
总结
- 准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性。最后将方法汇总构成一个不可改变的模板方法。
模板方法模式的适用场景
- 算法或操作遵循相似的逻辑
- 重构时(把相同的代码抽取到父类中)
- 重要、复杂的算法,核心算法设计为模板算法
模板方法模式的优缺点
优点
- 封装性好
- 复用性好
- 屏蔽细节
- 便于维护
缺点
- 由于java语言单继承的特性,一个类已经继承了某一个类,那么我们无法在使用继承的方式实现模板方法模式
模板方法模式的基本实现
饮料调制的小例子
- 基本过程
咖啡 | 茶 |
---|---|
把水煮沸 | 把水煮沸 |
用沸水冲泡咖啡 | 用沸水浸泡茶叶 |
把咖啡倒进杯子 | 把咖啡倒进杯子 |
加糖和牛奶 | 加柠檬 |
- 抽取相似过程
把水煮沸(boilWater) |
---|
泡饮料(brew) |
把饮料倒进杯子(pourInCup) |
加调味料(addCondiments) |
RefreshBeverage抽象基类
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname RefreshBeverage[提神饮料]
* @description 抽象基类,为所有子类提供算法框架
* @date 2020/09/12 8:48
*/
public abstract class RefreshBeverage {
/*
* 制作饮料的模板方法
* 封装了所有子类共同遵循的算法框架
* @description [抽象基类定义了算法框架,final关键字禁止子类对算法框架做任何改变、阻止子类对父类方法复写]
* @author xianping
* @date 2020/9/12
* @return void
**/
public final void prepareBeverageTemplate() {
//步骤1:将水煮沸
boilWater();
//步骤2:泡制饮料
brew();
//步骤3:将饮料倒入杯中
pourInCup();
//步骤4:加入调味料
addCondiMents();
}
/*
* @description 将水煮沸[公用方法,声明为private,减少子类复写工作量]
* @author xianping
* @date 2020/9/12
* @return void
**/
private void boilWater() {
System.out.println("将水煮沸");
}
/*
* @description 泡制饮料[需要子类进行实现的方法定义为abstract,具体实现由子类进行]
* @author xianping
* @date 2020/9/12
* @return void
**/
abstract void brew();
/*
* @description 将饮料倒入杯中[公用方法,声明为private,减少子类复写工作量]
* @author xianping
* @date 2020/9/12
* @return void
**/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/*
* @description 加入调味料[需要子类进行实现的方法定义为abstract,具体实现由子类进行]
* @author xianping
* @date 2020/9/12
* @return void
**/
abstract void addCondiMents();
}
Coffee实现类
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname Coffee 具体子类、咖啡类
* @description
* @date 2020/09/12 9:10
*/
public class Coffee extends RefreshBeverage {
/*
* @description 将饮料倒入杯中
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void brew() {
System.out.println("用沸水冲泡咖啡");
}
/*
* @description 加入调味料
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void addCondiMents() {
System.out.println("加入糖和牛奶");
}
}
Tea实现类
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname Coffee 具体子类、咖啡类
* @description
* @date 2020/09/12 9:10
*/
public class Tea extends RefreshBeverage {
/*
* @description 将饮料倒入杯中
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void brew() {
System.out.println("用80度热水浸泡茶叶5分钟");
}
/*
* @description 加入调味料
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void addCondiMents() {
System.out.println("加入柠檬");
}
}
测试方法
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname RefreshBeverageTest
* @description
* @date 2020/09/12 9:14
*/
public class RefreshBeverageTest {
public static void main(String[] args) {
//制作咖啡
RefreshBeverage coffee = new Coffee();
coffee.prepareBeverageTemplate();
//制作茶
RefreshBeverage tea = new Tea();
tea.prepareBeverageTemplate();
}
}
结果
将水煮沸
用沸水冲泡咖啡
将饮料倒入杯中
加入糖和牛奶
将水煮沸
用80度热水浸泡茶叶5分钟
将饮料倒入杯中
加入柠檬
钩子函数
如果我们想制作一杯不加任何调料的饮料,那么现有的模板是无法满足的
这时可以引入一个钩子函数,子类可选择重写父类钩子函数从而达到制作无任何调味料的饮料
RefreshBeverage抽象基类
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname RefreshBeverage[提神饮料]
* @description 抽象基类,为所有子类提供算法框架
* @date 2020/09/12 8:48
*/
public abstract class RefreshBeverage {
/*
* 制作饮料的模板方法
* 封装了所有子类共同遵循的算法框架
* @description [抽象基类定义了算法框架,final关键字禁止子类对算法框架做任何改变、阻止子类对父类方法复写]
* @author xianping
* @date 2020/9/12
* @return void
**/
public final void prepareBeverageTemplate() {
//步骤1:将水煮沸
boilWater();
//步骤2:泡制饮料
brew();
//步骤3:将饮料倒入杯中
pourInCup();
//步骤4:加入调味料
if (isCustomerWantsCondiments()) {
addCondiMents();
}
}
/*
*Hook方法,钩子函数。提供一个默认实现或为空的实现,具体的子类可以自行决定是否挂钩以及如何挂钩
* @description 询问用户是否加入调料[protected子类可以进行重写,非子类无法进行重写]
* @author xianping
* @date 2020/9/12
* @return boolean
**/
protected boolean isCustomerWantsCondiments() {
return true;
}
/*
* @description 将水煮沸[公用方法,声明为private,减少子类复写工作量]
* @author xianping
* @date 2020/9/12
* @return void
**/
private void boilWater() {
System.out.println("将水煮沸");
}
/*
* @description 泡制饮料[需要子类进行实现的方法定义为abstract,具体实现由子类进行]
* @author xianping
* @date 2020/9/12
* @return void
**/
abstract void brew();
/*
* @description 将饮料倒入杯中[公用方法,声明为private,减少子类复写工作量]
* @author xianping
* @date 2020/9/12
* @return void
**/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/*
* @description 加入调味料[需要子类进行实现的方法定义为abstract,具体实现由子类进行]
* @author xianping
* @date 2020/9/12
* @return void
**/
abstract void addCondiMents();
}
Tea实现类
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname Coffee 具体子类、咖啡类
* @description
* @date 2020/09/12 9:10
*/
public class Tea extends RefreshBeverage {
/*
* @description 将饮料倒入杯中
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void brew() {
System.out.println("用80度热水浸泡茶叶5分钟");
}
/*
* @description 加入调味料
* @author xianping
* @date 2020/9/12
* @return void
**/
@Override
void addCondiMents() {
System.out.println("加入柠檬");
}
/*
* @description 子类选择挂载钩子函数,通过重写的父类钩子函数,提供一个符合自己的实现
* @author xianping
* @date 2020/9/12
* @return boolean
**/
@Override
protected boolean isCustomerWantsCondiments() {
return false;
}
}
RefreshBeverageTest
package com.example.java_study.template_method;
/**
* @author xianping
* @version 1.0
* @classname RefreshBeverageTest
* @description
* @date 2020/09/12 9:14
*/
public class RefreshBeverageTest {
public static void main(String[] args) {
//制作茶
RefreshBeverage tea = new Tea();
tea.prepareBeverageTemplate();
}
}
结果
将水煮沸
用80度热水浸泡茶叶5分钟
将饮料倒入杯中