設計模式學習(八) 模板方法模式
引入
定義:在一個方法中定義了一個演算法的骨架,而將一些一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法介面的情況下,重新定義演算法中的某些步驟。
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.工廠方法是模板方法的一種特殊版本