茶和咖啡是兩個功能類似的類:咖啡的制作包括boil water, brew, pour, add condiments四步,而沏茶的過程包括boil water, steep, pour, add lemon四步。
它們的一,三兩步相同,二,四兩步類似,如下:
class Coffee {
public void boilWater() {
System.out.println("Boiling water");
}
public void brew() {
System.out.println("Brewing Coffee in the water");
}
public void pour() {
System.out.println("Pour Coffee in cup");
}
public void addCondiments() {
System.out.println("Add sugar and Milk in the coffee");
}
}
class Tea {
public void boilWater() {
System.out.println("Boiling water");
}
public void steep() {
System.out.println("Steep te a in the water");
}
public void pour() {
System.out.println("Pour tea in cup");
}
public void addLemon() {
System.out.println("Add lemon in the tea");
}
}
為了減少重復,增加復用,我們給它們加一個共同的抽象父類:CaffeineBeverage。
兩個子類中相同的部分移到了父類中,變成父類的方法;不同的部分在父類中寫成抽象方法,具體實現放到子類中(對比工廠方法,工廠方法是把對象的創建方法放到了子類中)。
另外,添加了一個prepareRecipe方法,相當于“一鍵”功能(這個和façade pattern的一鍵功能是有區別的,這里“一鍵”是寫在抽象父類中,而在façade pattern里,是寫在客戶端和類庫之間的一個新增的類中)。如下:
package javaapplication36;
public class Main {
public static void main(String[] args) {
CaffeineBeverage beverage = new Coffee();
beverage.prepareRecipe();
beverage = new Tea();
beverage.prepareRecipe();
}
}
abstract class CaffeineBeverage {
public void prepareRecipe() {
boilWater();
brew();
pour();
addCondiments();
}
public void boilWater() {
System.out.println("Boiling water");
}
public abstract void brew();
public void pour() {
System.out.println("Pour into the cup");
}
public abstract void addCondiments();
}
class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Brewing Coffee in the water");
}
public void addCondiments() {
System.out.println("Add sugar and Milk in the coffee");
}
}
class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steep the tea");
}
public void addCondiments() {
System.out.println("Add lemon in the tea");
}
}
在以上代碼中:父類的prepareRecipe方法實際上就是所謂的Template 方法,template方法中所調用的其他函數,有的在父類中已經實現,有的會根據不同的子類,而采用不同的實現。
Template pattern:即算法(prepareRecipe方法)在父類中寫好了,會調用父類里的各種方法來實現(boilingwater,brew,pour,addCondiments),這些方法有的會在父類中就已經實現,有的會放到子類中實現之。
再把程序寫的更完善些:由于算法prepareRecipe是不會變動的,所以我們給它加上final,而對于addCondiments ,有的飲料有可能需要配料,而有的飲料根本不可能添加配料。(也就是說有的子類, prepareRecipe方法中需要執行addCondiments功能,而有的子類不需要。)
為了不寫幾種重復prepareRecipe方法(prepareRecipe1, prepareRecipe2….每種方法只是有個別步驟不一樣),我們使用Hook方式來做(Hook就是指一個包含空內容的實體方法,不同于抽象方法),如下:
package javaapplication36;
public class Main {
public static void main(String[] args) {
CaffeineBeverage beverage = new Coffee();
beverage.prepareRecipe();
beverage = new Tea();
beverage.prepareRecipe();
beverage = new Milk();
beverage.prepareRecipe();
}
}
abstract class CaffeineBeverage {
public void prepareRecipe() {
boilWater();
brew();
pour();
addCondiments();
}
public void boilWater() {
System.out.println("Boiling water");
}
public abstract void brew();
public void pour() {
System.out.println("Pour into the cup");
}
public void addCondiments() { //HOOK,一個空方法,不做任何事情,繼承的子類根據需//要覆蓋或者保持它為空。
}
}
class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Brewing Coffee in the water");
}
public void addCondiments() { //對addCondiments重寫
System.out.println("Add sugar and Milk in the coffee");
}
}
class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steep the tea");
}
public void addCondiments() { //對addCondiments重寫
System.out.println("Add lemon in the tea");
}
}
class Milk extends CaffeineBeverage { //沒有對addCondiments重寫
public void brew() {
System.out.println("Brewing Milk in the water.");
}
}
最后,我們試圖更進一步完善代碼,即使子類有addCondiments這個方法,我們也可以根據客戶端的需要,選擇在執行prepareRecipe的時候是否進行addCondiments這一步。
package javaapplication36;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Main {
public static void main(String[] args) {
CaffeineBeverage beverage = new Coffee();
beverage.prepareRecipe();
beverage = new Tea();
beverage.prepareRecipe();
beverage = new Milk();
beverage.prepareRecipe();
}
}
abstract class CaffeineBeverage {
public void prepareRecipe() {
boilWater();
brew();
pour();
if (customerWantCondiments()) {
addCondiments();
}
}
public void boilWater() {
System.out.println("Boiling water");
}
public abstract void brew();
public void pour() {
System.out.println("Pour into the cup");
}
public void addCondiments() {
}
public boolean customerWantCondiments() {
return true;//相當于不做任何判斷就直接讓prepareRecipe執行addCondiments()
}
}
class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Brewing Coffee in the water");
}
@Override
public void addCondiments() {
System.out.println("Add sugar and Milk in the coffee");
}
@Override
public boolean customerWantCondiments() {
String answer = getUserInput();
if (answer.startsWith("y")) {
return true;
}
else {
return false;
}
}
private String getUserInput() { //獲取輸入,確定是否要Condiments
System.out.println("Would you love some condiments? (y/n)");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String answer = null;
try {
answer = in.readLine();
}
catch (IOException ex) {
Logger.getLogger(Coffee.class.getName()).log(Level.SEVERE, null, ex);
}
answer = answer.toLowerCase();
return answer;
}
}
class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steep the tea");
}
@Override
public void addCondiments() {
System.out.println("Add lemon in the tea");
}
}
class Milk extends CaffeineBeverage {
public void brew() {
System.out.println("Brewing Milk in the water.");
}
}
再舉一例,很常見的,自定義類型數組的比較就是用到了Template pattern,如下:
package javaapplication37;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Duck[] ducks = {new Duck("duck1", 10), new Duck("duck2", 13), new Duck("duck3", 9)};
displayDucks(ducks);
Arrays.sort(ducks);//Arrays相當于template pattern中的抽象父類,sort是該類中的//算法函數
displayDucks(ducks);
}
private static void displayDucks(Duck[] ducks) {
for (int i = 0; i < ducks.length; i++) {
System.out.println(ducks[i].toString());
}
}
}
class Duck implements Comparable {
String name;
int weight;
Duck(String name, int weight) {
this.name = name;
this.weight = weight;
}
@Override
public String toString() {
return name + ":" + weight;
}
public int compareTo(Object o) { //compareTo方法相當于HOOK
Duck otherDuck = (Duck) o;
if (this.weight < otherDuck.weight) {
return -1;
}
else if (this.weight == otherDuck.weight) {
return 0;
}
else {
return 1;
}
}
}
我們來看看Arrays這個類是如何使用template pattern的。
public class Arrays {
…
…
public static void sort(Object[] a) { //sort是算法函數
Object[] aux = (Object[])a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
…
…
}
}
public interface Comparable<T> {
public int compareTo(T o); //compareTo是HOOK
}
再舉一例,看看Swing中的JFrame是如何使用template pattern的:
package javaapplication38;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
MyFrame frame = new MyFrame("my first frame");
}
}
class MyFrame extends JFrame { //JFrame 相當于template pattern中的父類,而它里面的//update方法就是其算法函數
public MyFrame(String title) {
super(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300, 300);
this.setVisible(true);
}
public void paint(Graphics g) { //paint是HOOK
super.paint(g);
String msg = "i rule";
g.drawString(msg, 100, 50);
}
}