模板方法模式(Template Method Design Pattern)


目的
在操作中定义算法的框架,将一些步骤推迟到子类。模板方法允许子类重新定义算法的某些步骤而不改变算法的结构。

结构

参与者
1. AbstractClass

  • 定义具体子类定义的实现算法步骤的抽象的基本操作。

  • 实现定义算法框架的模板方法。模板方法调用基本操作以及在AbstractClass或其他对象中定义的操作。

2. ConcreteClass 

  • 实现基本操作以执行算法的子类特定步骤。

合作

  • ConcreteClass依赖于AbstractClass来实现算法的恒定步骤。
  •  

源代码
按照步骤实现模板方法模式。
让我们创建一个咖啡饮料系统

步骤1:创建  CaffeineBeverage类,该类定义算法的骨架。

/**
 * CaffeineBeverage defines skeleton for the algorithm.
 * @author RAMESH
 *
 */

public abstract class CaffeineBeverage {
  
 final void prepareRecipe() {
  boilWater();
  brew();
  pourInCup();
  addCondiments();
 }
 
 abstract void brew();
  
 abstract void addCondiments();
 
 void boilWater() {
  System.out.println(
"Boiling water");
 }
  
 void pourInCup() {
  System.out.println(
"Pouring into cup");
 }
}

步骤2:创建一个Coffee类,它扩展了抽象的CaffeineBeverage类并实现了抽象方法。

public class Coffee extends CaffeineBeverage {
 public void brew() {
  System.out.println("Dripping Coffee through filter");
 }
 public void addCondiments() {
  System.out.println(
"Adding Sugar and Milk");
 }
}

步骤3:创建一个Tea类,它扩展了抽象的CaffeineBeverage类并实现了抽象方法。

public class Tea extends CaffeineBeverage {
 public void brew() {
  System.out.println("Steeping the tea");
 }
 public void addCondiments() {
  System.out.println(
"Adding Lemon");
 }
}

步骤4:使用钩子(hook),我们可以重写一个方法,或者不重写,这是我们的选择,如果我们不重写,那么抽象类提供默认实现。

public abstract class CaffeineBeverageWithHook {
 
 void prepareRecipe() {
  boilWater();
  brew();
  pourInCup();
  if (customerWantsCondiments()) {
   addCondiments();
  }
 }
 
 abstract void brew();
 
 abstract void addCondiments();
 
 void boilWater() {
  System.out.println("Boiling water");
 }
 
 void pourInCup() {
  System.out.println(
"Pouring into cup");
 }
 
 boolean customerWantsCondiments() {
  return true;
 }
}

步骤5:让我们覆盖hook()方法并提供自己的实现。

import java.io.*;

public class CoffeeWithHook extends CaffeineBeverageWithHook {
 
 public void brew() {
  System.out.println("Dripping Coffee through filter");
 }
 
 public void addCondiments() {
  System.out.println(
"Adding Sugar and Milk");
 }
 
 public boolean customerWantsCondiments() {

  String answer = getUserInput();

  if (answer.toLowerCase().startsWith(
"y")) {
   return true;
  } else {
   return false;
  }
 }
 
 private 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 ioe) {
   System.err.println(
"IO error trying to read your answer");
  }
  if (answer == null) {
   return
"no";
  }
  return answer;
 }
}

import java.io.*;

public class TeaWithHook extends CaffeineBeverageWithHook {
 
 public void brew() {
  System.out.println("Steeping the tea");
 }
 
 public void addCondiments() {
  System.out.println(
"Adding Lemon");
 }
 
 public boolean customerWantsCondiments() {

  String answer = getUserInput();

  if (answer.toLowerCase().startsWith(
"y")) {
   return true;
  } else {
   return false;
  }
 }
 
 private String getUserInput() {
 
// get the user's response
  String answer = null;

  System.out.print(
"Would you like lemon with your tea (y/n)? ");

  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  try {
   answer = in.readLine();
  } catch (IOException ioe) {
   System.err.println(
"IO error trying to read your answer");
  }
  if (answer == null) {
   return
"no";
  }
  return answer;
 }
}

步骤6:测试上面的实现。
让我们创建一个热咖啡和热茶,这里客户需要通过输入来决定他/她是否想要调味品。

public class BeverageTestDrive {
 public static void main(String args) {
 
  Tea tea = new Tea();
  Coffee coffee = new Coffee();
 
  System.out.println("\nMaking tea...");
  tea.prepareRecipe();
 
  System.out.println(
"\nMaking coffee...");
  coffee.prepareRecipe();

 
  TeaWithHook teaHook = new TeaWithHook();
  CoffeeWithHook coffeeHook = new CoffeeWithHook();
 
  System.out.println(
"\nMaking tea...");
  teaHook.prepareRecipe();
 
  System.out.println(
"\nMaking coffee...");
  coffeeHook.prepareRecipe();
 }
}

输出:

Making tea...
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon

Making coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and Milk

Making tea...
Boiling water
Steeping the tea
Pouring into cup
Would you like lemon with your tea (y/n)? y
Adding Lemon

Making coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Would you like milk and sugar with your coffee (y/n)? y
Adding Sugar and Milk


关于模板方法模式的重点

  • 超级模板方法遵循“ 好莱坞原则 ”:“不要打电话给我们,我们会打电话给你”。这指的是,不是从子类中的基类调用方法,而是从超类的模板方法中调用子类中的方法。

  • 父类中的模板方法不应被覆盖,因此应使其成为最终方法。

  • 自定义钩子(hooks):包含在其他类中可能被覆盖的默认实现的方法称为钩子方法。钩子方法旨在被覆盖,具体方法不是。所以在这种模式中,我们可以提供钩子方法。问题有时候很难区分钩子方法和具体方法。

  • 模板方法是代码重用的技术,因此,您可以找出常见行为并将特定行为推迟到子类。

适用场景

  • 一次实现算法的不变部分,并将其可能变化的行为留给子类来实现。

  • 当子类之间的公共行为应该在公共类中进行分解和本地化以避免代码重复时,这是Opdyke和Johnson所描述的“重构到泛化”的好例子。首先识别现有代码中的差异,然后将差异分为新的操作。最后,使用调用其中一个新操作的模板方法替换不同的代码。

  • 控制子类扩展。您可以定义一个模板方法,在特定的点上调用“hook”操作,从而只允许在这些点上进行扩展。