思路
父类(抽象类)中定义处理流程的框架(模板方法)和模板中需要的方法(基本方法),子类(具体类)中实现父类的基本方法。
例子
- 煮咖啡有四个步骤
public class Coffee {
void prepareRecipe(){
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
void boilWater(){
System.out.println("Boiling water...");
}
public void brewCoffeeGrinds(){
System.out.println("Dripping Coffee through filter...");
}
void pourInCup(){
System.out.println("Pouring into Cup...");
}
public void addSugarAndMilk(){
System.out.println("Adding Sugar and Milk...");
}
}
- 泡茶也有四个类似的步骤,但有些实现方法不同
public class Tea {
void prepareRecipe(){
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
void boilWater(){
System.out.println("Boiling water...");
}
public void steepTeaBag(){
System.out.println("Steeping the tea...");
}
void pourInCup(){
System.out.println("Pouring into Cup...");
}
public void addLemon(){
System.out.println("Adding Lemon...");
}
}
- 将两者的流程框架作为模板方法,并把相同的方法提取出来。
public abstract class CaffeineBeverage {
/**
*
* @desc
* 模板方法,用来控制泡茶与冲咖啡的流程
* 申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
* @return void
*/
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
/**
* @desc
* 将brew()、addCondiment()声明为抽象方法,具体操作由子类实现
* @return void
*/
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water...");
}
void pourInCup(){
System.out.println("Pouring into Cup...");
}
}
- 分别用两个子类继承该模板类,并根据不同的需求重写相应的方法。
【泡咖啡】
public class Coffee extends CaffeineBeverage {
void addCondiments() {
System.out.println("Adding Sugar and Milk...");
}
void brew() {
System.out.println("Dripping Coffee through filter...");
}
}
【煮茶】
public class Tea extends CaffeineBeverage{
void addCondiments() {
System.out.println("Adding Lemon...");
}
void brew() {
System.out.println("Steeping the tea...");
}
}
【测试类】
public class Test {
public static void main(String[] args) {
Tea tea = new Tea();
tea.prepareRecipe();
}
}
类的关系
可以看出,抽象类提供一组算法和部分逻辑的实现,具体子类实现剩余逻辑。
钩子
如果某些客户并不喜欢加入调料,而喜欢原生态的,但是我们的算法总是会给客户加入调料,怎么解决?
遇到这个问题我们可以使用钩子。所谓钩子就是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以使子类能够对算法的不同点进行挂钩,即让子类能够对模板方法中某些即将发生变化的步骤做出相应的反应。当然要不要挂钩,由子类决定。
所以对于上面的要求,我们可以做出如下修改。
public abstract class CaffeineBeverageWithHook {
void prepareRecipe(){
boilWater();
brew();
pourInCup();
if(customerWantsCondiments()){ //如果顾客需要添加调料,我们才会调用addCondiments()方法
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water...");
}
void pourInCup(){
System.out.println("Pouring into Cup...");
}
public boolean customerWantsCondiments(){
return true;
}
}
而客户是否需要加入调料,只需要回答y或者n
public class CoffeeWithHook extends CaffeineBeverageWithHook{
void addCondiments() {
System.out.println("Adding Sugar and Milk...");
}
void brew() {
System.out.println("Dripping Coffee through filter...");
}
/**
* 覆盖该钩子,提供自己的实现方法
*/
public boolean customerWantsCondiments(){
if("y".equals(getUserInput().toLowerCase())){
return true;
}
else{
return false;
}
}
public String getUserInput(){
String answer = null;
System.out.print("Would you like milk and sugar with your coffee(y/n):");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if(answer == null){
return "n";
}
return answer;
}
}
测试代码
public class Test {
public static void main(String[] args) {
CoffeeWithHook coffeeHook = new CoffeeWithHook();
coffeeHook.prepareRecipe();
}
}
运行结果
优缺点
优点
-
模板方法模式在定义了一组算法,将具体的实现交由子类负责。
-
模板方法模式是一种代码复用的基本技术。
-
模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
缺点
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,是的系统更加庞大。
使用场景
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 控制子类的扩展。
模式总结
- 模板方法模式定义了算法的步骤,将这些步骤的实现延迟到了子类。
- 模板方法模式为我们提供了一种代码复用的重要技巧。
- 模板方法模式的抽象类可以定义抽象方法、具体方法和钩子。
- 为了防止子类改变算法的实现步骤,我们可以将模板方法声明为 final。