模板方法模式。
案例
泡茶和冲咖啡的步骤很相似,咖啡的步骤:
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒进杯子里
- 加糖和牛奶
泡茶的步骤:
- 把水煮沸
- 用沸水浸泡茶叶
- 把茶倒进杯子
- 加柠檬
所以咖啡的代码如下:
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
public 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();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeaBag() {
System.out.println("Steeping the tea");
}
public void addLemon() {
System.out.println("Adding Lemon");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
这很明显的重复代码,所以我们可以提取公共的部分:
- 把水煮沸
- 用热水泡咖啡或茶
- 把饮料倒进杯子
- 在饮料内加入适当的调料
第一步和第三步是公共的,可以抽出来,而第二步和第四步不一样的地方也只有饮料或调料不一样而已。
我们先把 1、3 两步抽取到超类中。
public abstract class CaffeineBeverage {
public abstract void prepareRecipe();
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
但 2、4 两步也是很相似的,所以我们也在超类中定义,修改后再看。
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
protected abstract void addCondiments();
protected abstract void brew();
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
我们把 2、4 两步抽象化,等子类去实现他们的具体功能。改造后的茶和咖啡如下。
public class Tea extends CaffeineBeverage {
@Override
protected void addCondiments() {
System.out.println("Adding Lemon");
}
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
}
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");
}
}
茶和咖啡都是继承自咖啡因饮料,他们都需要自己实现 brew() 和 addCondiments() 方法。这就是模板方法模式,preareRecipe() 是我们的模板方法,它被用作一个算法的模板,这个算法指的是制作咖啡因饮料的。算法的每一个步骤都被一个方法代表了。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
定义
模板方法模式在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
其实就是抽出几个相似的类中具有的类似的步骤,这些步骤都可以是抽象的,由子类实现,这可以确保算法的结构保持不变,同时由子类提供部分实现。
我们也可以进行钩子的设定,声明到我们的抽象类里中空的或默认实现的方法。可以留给子类扩展空间,让子类有能力对算法的不同点进行不一样的步骤,具体要不要挂钩,由子类决定。下面我们先是一个例子:
public abstract class CaffeineBeverageWithHook {
void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
private boolean customerWantsCondiments() {
return true;
}
protected abstract void addCondiments();
protected abstract void brew();
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
我们加上了一个条件语句,条件是由 customerWantsCondiments() 方法的返回值决定的。如果顾客想要调料,返回 true,执行 addCondiments() 方法,这就是一个钩子,子类可以覆盖这个方法,但不见得必须覆盖。我们继续修改我们的代码:
public class CoffeeWithHook extends CaffeineBeverageWithHook {
@Override
protected void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
@Override
protected void brew() {
System.out.println("Dripping Coffee through filter");
}
public boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}
private String getUserInput() {
String answer = null;
System.out.println("Would you like milk and sugar with your coffee (y/n)?");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
answer = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (answer == null) {
return "no";
}
return answer;
}
}
首先我们实现了 brew() 和 addCondiments() 方法,然后覆盖了 customerWantsCondiments() 方法,用户输入 y 代表用户需要添加奶和糖,n 反之,现在我们执行测试程序。
public class BeverageTestDrive {
public static void main(String[] args) {
CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.prepareRecipe();
}
}
钩子很方便吧,其实我们也可以直接将它设置到超类中。
我们现在又有了一个新的设计原则:「好莱坞原则」。
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是「别调用我们,我们会调用你」。
Java 中的模板方法模式
用模板方法排序,数组的 sort(Object[] a) 方法是排序方法我们来看一下它的代码。
public static void sort(Object[] a) {
Object[] aux = (Object[])a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
这个方法只是一个辅助方法,用来创建一个数组的拷贝,然后将其传递给 mergeSort() 方法。
private static void mergeSort(Object src[], Object dest[], int low, int high, int off) {
for (int i=low; i<high; i++) {
for (int j=i; j>low && ((Comparable)dest[j-1].compareTo((Comparable)dest[j])>0; j--) {
swap(dest, j, j-1);
}
}
return;
}
这个方法包含排序算法,此算法依赖于 compareTo() 方法实现完成算法。这里就可以想成是一个模板方法,需要实现 compareTo() 方法。现在来看一个例子。比较人的年龄,我们需要一个人的类。
public class People implements Comparable {
String name;
int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
People o1 = (People) o;
return Integer.compare(this.age, o1.age);
}
}
测试程序。
public class PeopleSortTestDrive {
public static void main(String[] args) {
People[] people = {
new People("Ab", 12),
new People("zhangsan", 123),
new People("lisi", 22),
new People("wangwu", 53),
new People("zhaoliu", 66)
};
System.out.println("Before sorting: ");
display(people);
Arrays.sort(people);
System.out.println("\nAfter sorting:");
display(people);
}
public static void display(People[] people) {
for (People person : people) {
System.out.println(person);
}
}
}
第一次是无序的打印,第二次是从小到大的顺序。这就说明我们的排序是成功的,关键步骤是我们实现的 Comparable 接口中的 compareTo 方法,我们通过 Integer 类中的 compare(int x, int y) 方法实现比较,所以最终整个数组能完美的排序完成。
写一个 Swing 的窗口程序
下一个例子是 Swing 的 JFrame!JFrame 是最基本的 Swing 容器,继承了一个 paint() 方法,默认状态下 paint() 是不做事情的,因为它是一个钩子,覆盖它,我们就能把自己的代码插入到 JFrame 的算法中。
public class MyFrame extends JFrame {
public MyFrame(String title) {
super(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300, 300);
this.setVisible(true);
}
public void paint(Graphics graphics) {
super.paint(graphics);
String msg = "I rule!!";
graphics.drawString(msg, 100,100);
}
public static void main(String[] args) {
new MyFrame("Head First Design Patterns");
}
}
我们利用了 paint() 钩子方法,加入了我们自己定义的代码,也是外观模式的应用。