1. 程式人生 > >設計模式學習(八) 模板方法模式

設計模式學習(八) 模板方法模式

引入

定義:在一個方法中定義了一個演算法的骨架,而將一些一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法介面的情況下,重新定義演算法中的某些步驟。

uml類圖

這個模式是用來建立一個演算法的模板,什麼是模板?如你所見的,模板就是一個方法。更具體地說,這個方法將演算法定義成一組步驟,其中任何步驟都可以使抽象的,由子類負責實現,這樣可以確保演算法的結構保持不變,同時由子類提供部分實現。

示例

引用head first上的一個例子"咖啡沖泡手冊"

咖啡沖泡法

1.把水煮沸

2.用沸水沖泡咖啡

3.把咖啡倒進杯子

4.加糖和牛奶

茶水沖泡法

1.把水煮沸

2.把沸水浸泡茶葉

3.把茶倒進杯子

4.加檸檬

我們可以看出兩種沖泡方法,流程基本一致,我們將相同的行為提取出來,將類似的行為向上泛化,有子類完成具體的實現

抽象後:

1.把水煮沸

2.沖泡

3.倒進杯子

4.加料

package com.zpkj.project16;

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");
	}

}
package com.zpkj.project16;

public class Coffee extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Dripping Coffee through filter");
	}

	@Override
	void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}

}
package com.zpkj.project16;

public class Tea extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Steeping the tea");
		
	}

	@Override
	void addCondiments() {
		System.out.println("Adding lemon");
		
	}

}
package com.zpkj.project16;

public class Cilent {
	
	public static void main(String[] args) {
		CaffeineBeverage beverage1 = new Tea();
		CaffeineBeverage beverage2 = new Coffee();
		beverage1.prepareRecipe();
		System.out.println("-------------------");
		beverage2.prepareRecipe();
	}
	
}

結果

使用場景:

  多個子類有共有的方法,並且邏輯基本相同時; 重要、複雜的演算法,可以把核心演算法設計成模板方法,周邊相關細節功能則由各個子類實現; 重構時,模板方法模式是一個經常使用的模式,把相同的程式碼抽取到父類中,然後通過鉤子函式約束其行為。

好處:

1.程式碼複用

2.保證演算法結構不變,無需關心具體的實現

使用鉤子

當子類必須提供演算法中的實現時,就可以使用抽象方法,如果演算法中的某個部分是 可選的,子類可以選擇實現這個鉤子。

改造上面的抽象模板程式碼

package com.zpkj.project16;

public abstract class CaffeineBeverage {
	
	final void prepareRecipe(){
		boilWater();
		brew();
		pourInCup();
		if(isCondiments()){
			addCondiments();			
		}
	}
	//將下層行為向上抽象
	abstract void brew();
	
	abstract void addCondiments();
	
	
	void boilWater(){
		System.out.println("Boiling water");
	}
	
	void pourInCup(){
		System.out.println("Pouring into cup");
	}
	
	boolean isCondiments(){
		return true;
	}

}

我們想要不加佐料的咖啡,在子類中覆蓋鉤子方法,

package com.zpkj.project16;

public class Coffee extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Dripping Coffee through filter");
	}

	@Override
	void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}

	@Override
	boolean isCondiments() {
		return false;
	}

}

結果

oo原則

1::好萊塢原則:別調用(打電話給)我們,我們會呼叫(打電話給)你。

好萊塢原則給我們一種防止“依賴腐敗”的方法。當高層主鍵依賴底層元件,而底層元件又依賴高層元件,而高層元件又依賴邊界元件,邊界元件又依賴底層元件時,系統設計就變的非常之複雜。

在好萊塢原則下,允許底層元件將自己掛鉤到系統上,高層元件決定怎麼使用這些底層元件!

"別調用我們,我們會呼叫你"

好萊塢原則和依賴倒置原則

1.依賴倒置教我們儘量避免使用具體類,而多實用抽象。

2.好萊塢原則是用在 建立框架或元件上的一種技巧,好讓低層元件能夠被掛鉤到計算中,而不會讓高層元件依賴底層元件。

兩者的目標都在於解耦

jdk中的模板方法

在Arrays類中,提供了陣列操作的排序方法。

 public static void sort(Object[] a) {
        Object[] aux = (Object[])a.clone();
        mergeSort(aux, a, 0, a.length, 0);
    }

mergeSort方法包含排序演算法,此演算法依賴於compartTo()方法的實現來完成演算法

模板方法

 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;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

示例

package com.zpkj.array;

public class Duck implements Comparable<Duck>{
	
	String name;
	int weight;
	
	public Duck(String name,int weight) {
		super();
		this.name = name;
		this.weight = weight;
	}
	
	@Override
	public int compareTo(Duck o) {
		if(this.weight<o.weight){
			return -1;
		}else if(this.weight == o.weight){
			return 0;
		}else{
			return 1;
		}
	}

	@Override
	public String toString() {
		return "Duck [name=" + name + ", weight=" + weight + "]";
	}

}
package com.zpkj.array;

import java.util.Arrays;

public class Main {
	public static void main(String[] args) {
		Duck duck1 = new Duck("黃鴨子", 6);
		Duck duck2 = new Duck("綠鴨子", 3);
		Duck duck3 = new Duck("紅鴨子", 8);
		Duck duck4 = new Duck("紫鴨子", 5);
		Duck[] ducks = new Duck[]{duck1,duck2,duck3,duck4};
		Arrays.sort(ducks);
		for(Duck e:ducks){
			System.out.println(e.toString());
		}
		
	}

}

結果

總結

1.模板方法定義了演算法的步驟,把這些步驟的實現延遲到子類。

2.模板方法模式為我們提供了一種程式碼複用的重要技巧

3.模板方法的抽象類可以定義具體方法,抽象方法和鉤子

4.抽象方法由子類實現

5.鉤子是一種方法,它在抽象類中不做事,或者只做預設的事情,子類可以選擇是否覆蓋它

6.為防止子類改變模板方法中的演算法,可以將模板方法宣告為final

7.好萊塢原則告訴我們。將決策權放在高層模板中,以便決定如何以及何時呼叫低層模板

8.策略模式和模板fang方法模式都封裝演算法,一個用組合,一個用繼承

9.工廠方法是模板方法的一種特殊版本

原始碼下載