八、模板方法模式
定义
模板方法模式:在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
UML类图
说明:
1、AbstractClass抽象中包含了模板方法,primitiveOpration1() 和primitiveOpration2() 是这个模板方法所用到的操作的抽象版本。
2、模板方法在实现算法的过程中,用到了两个原语操作。模板方法本身和这两个操作的具体实现之间被解耦了。
3、ConcreteClass 可以有多个具体类,每一个都实现了模板方法所需要的全部操作。ConcreteClass中的primitiveOpration1()和primitiveOpration2(),当模板方法需要这两个抽象方法时,会调用他们。
简单实现
AbstractMethod 抽象的模板方法定义
/**
* 模板方法模式在一个方法中定义了一个算法骨架,而将一些步骤延伸到子类中。
* 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
*/
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
//该方法在模板的模式中被称作原语操作 具体子类必须实现它们
protected abstract void addCondiments();
protected abstract void brew();
private final void pourInCup() {
System.out.println("pour water in cup...");
}
private final void boilWater() {
System.out.println("boil water...");
}
}
ConcreteClass 抽象模板方法子类的实现
public class Tea extends CaffeineBeverage {
@Override
protected void addCondiments() {
System.out.println("Adding Lemon...");
}
@Override
protected void brew() {
System.out.println("Adding Tea in Cup...");
}
}
public class Coffee extends CaffeineBeverage {
@Override
protected void addCondiments() {
System.out.println("Adding Sugar and Milk...");
}
@Override
protected void brew() {
System.out.println("Dripping Coffee through filter...");
}
}
测试类
public class BaristaTest {
public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("Making tea...");
tea.prepareRecipe();
System.out.println("Making coffee...");
coffee.prepareRecipe();
}
}
对模板方法进行挂钩操作
CaffeineBeverageWithHook 带钩子的抽象模板方法定义
public abstract class CaffeineBeverageWithHook {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
//条件判断语句,该条件是否成立,是有一个customerWantsCondiments()钩子方法决定的。
if (customerWantsCondiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
private final void boilWater() {
System.out.println("Boiling water");
}
private final void pourInCup() {
System.out.println("Pouring into cup");
}
//这是一个钩子,子类可以覆盖这个方法,但是不见得一定这么做
boolean customerWantsCondiments() {
return true;
}
}
使用钩子 CoffeeWithHook 和 TeaWithHook
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;
}
}
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() {
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;
}
}
测试代码
public class BeverageTestDrive {
public static void main(String[] args) {
TeaWithHook teaHook = new TeaWithHook();
CoffeeWithHook coffeeHook = new CoffeeWithHook();
System.out.println("\nMaking tea...");
teaHook.prepareRecipe();
System.out.println("\nMaking coffee...");
coffeeHook.prepareRecipe();
}
}
说明:钩子是一种被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。需不需要钩子,由子类自行决定。
模板方法在Java排序中的实现
Duck类的排序
public class Duck implements Comparable {
String name;
int weight;
public Duck(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String toString() {
return name + " weighs " + weight;
}
public int compareTo(Object object) {
Duck otherDuck = (Duck)object;
if (this.weight < otherDuck.weight) {
return -1;
} else if (this.weight == otherDuck.weight) {
return 0;
} else { // this.weight > otherDuck.weight
return 1;
}
}
}
测试方法
public class DuckSortTestDrive {
public static void main(String[] args) {
Duck[] ducks = {
new Duck("Daffy", 8),
new Duck("Dewey", 2),
new Duck("Howard", 7),
new Duck("Louie", 2),
new Duck("Donald", 10),
new Duck("Huey", 2)
};
System.out.println("Before sorting:");
display(ducks);
Arrays.sort(ducks);
System.out.println("\nAfter sorting:");
display(ducks);
}
public static void display(Duck[] ducks) {
for (int i = 0; i < ducks.length; i++) {
System.out.println(ducks[i]);
}
}
}
说明:
1、数组的排序模板算法已经提供了算法,但是要让这些模板方法知道如何比较duck,需要做的就是实现compareTo() 方法。
2、Array的设计者希望这个方法能够用于所有的数组,所以把sort()变成静态的方法,这样一来,任何数组都可以使用这个方法。
3、因为sort()并不是真正定义在超类中,所以sort()方法需要知道已经实现了这个compareTo()方法,否则无法进行排序。
4、compareTo()方法返回是一个int类型的变量,返回值为 1、0、-1 三种。
5、实现Comparable接口,并重写compareTo()方法。这种模板方法的实现,侧重在于提供一个算法,并让子类实现某些步骤,而数组的排序算法很明显正是如此。
6、在Duck类的排序算法中,由于无法继承Java数组,而sort()方法希望能够用于所有的数组(每个数组都是不同的类)。所以Arrays提供了一个静态方法,而由被排序对象内的每个元素自行提供比较大小的算法步骤。所以,这并不是标准的模板方法的实现,但是符合模板方法的模式精神。(由于不需要继承数组就可以实现排序算法,使得排序变得更有弹性、更有用)
7、在Java API的实现中,java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被 read(byte b[], int off, int len) 模板方法使用。
模板方法模式与策略模式的比较
1、数组对象的排序,这部分和策略模式相似,都是使用对象的组合。但是在策略模式中,所组合的类实现了整个算法,数组所实现的排序算法并不完整,他需要一个类填补compareTo()方法的实现。因此更像模板方法。
2、策略模式并不是使用继承来进行算法的实现,而是通过对象组合的方式,让客户可以选择算法的实现。而模板方法模式是定义一个算法大纲,而由子类定义其中某些步骤的内容。这样一来在算法的个别步骤可以有不同的细节实现,但算法的结构依然维持不变。
3、模板方法模式使用的重复代码,被放入到超类中,好让所有子类共享。而策略模式使用的是委托模型,使用对象组合,使得策略模式更加有弹性。利用策略模式,客户可以在运行时改变自己的算法,而客户所需要的,只是改用不同的策略对象罢了。
4、模板方法模式是经常被使用的模式,因为在超类中提供了一个基础的方法,达到代码的复用,并允许子类指定行为。这在创建框架的时候是非常有用的。
5、模板方法模式由于必须依赖超类中的方法的实现,算作算法的一部分。策略模式不需要依赖,整个算法自己去实现。所以依赖程度相对比较低。
要点
1、“模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类。
2、模板方法为我们提供了一种代码复用的技巧。
3、模板方法的抽象类可以定义具体的方法、抽象方法和钩子。
4、抽象方法由子类实现。
5、钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
6、为了防止子类改变模板方法中的算法,可以将模板方法声明为final
7、将决策权放在高层模块中,以便决定如何及何时调用低层模块。
8、在真实实现的模板方法中,模板方法由很多的变体,不要指望一眼辨认出来。
9、策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
10、工厂方法是模板方法的一种特殊版本。