關注公眾號 JavaStorm 獲取更多精彩。

模板方法模式在一個方法中定義了一個演算法骨架,並且 final 修飾防止子類重寫。方法中包含一些抽象方法,也就是一些步驟延遲到字類實現。模板方法使得在不改變演算法結構的情況下,重新定義演算法中的某些步驟。完整代碼可以查看GitHub:github.com/UniqueDong/z

類圖

模式實現

在實現模板方法模式時,開發抽象類的軟體設計師和開發具體子類的軟體設計師之間可以進行協作。一個設計師負責給出一個演算法的輪廓和框架,另一些設 計師則負責給出這個演算法的各個邏輯步驟。實現這些具體邏輯步驟的方法即為基本方法,而將這些基本方法匯總起來的方法即為模板方法,模板方法模式的 名字也因此而來。下面將詳細介紹模板方法和基本方法: 1. 模板方法 一個模板方法是定義在抽象類中的、把基本操作方法組合在一起形成一個總演算法或一個總行為的方法。這個模板方法定義在抽象類中,並由子類不加以修改 地完全繼承下來。模板方法是一個具體方法,它給出了一個頂層邏輯框架,而邏輯的組成步驟在抽象類中可以是具體方法,也可以是抽象方法。由於模板方法 是具體方法,因此模板方法模式中的抽象層只能是抽象類,而不是介面。 2. 基本方法 基本方法是實現演算法各個步驟的方法,是模板方法的組成部分。基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。 (1) 抽象方法:一個抽象方法由抽象類聲明、由其具體子類實現。 (2) 具體方法:一個具體方法由一個抽象類或具體類聲明並實現,其子類可以進行覆蓋也可以直接繼承。 (3) 鉤子方法:一個鉤子方法由一個抽象類或具體類聲明並實現,而其子類可能會加以擴展。通常在父類中給出的實現是一個空實現,並以該空實 現作為方法的默認實現,當然鉤子方法也可以提供一個非空的默認實現。 鉤子可以讓子類實現演算法中可選的部分,或者在鉤子對於子類的實現並不重要的時候,子類可以對此鉤子置之不理。鉤子的另一個用法,是讓子類能夠有機會 對模板方法中某些即將發生的(或剛剛發生的)步驟做出反應。

使用場景

開一家咖啡、茶館,泡茶和咖啡的沖泡方式非常相似:

星巴克咖啡沖泡法

  1. 把水煮沸
  2. 用沸水沖泡咖啡
  3. 把咖啡倒進杯子
  4. 加糖和牛奶

功夫茶沖泡法

  1. 把水煮沸
  2. 用沸水沖泡茶葉
  3. 把茶倒進杯子
  4. 加檸檬

我們可以發現兩種茶的步驟1,和步驟3是一樣的。整體演算法結構是固定的,只是有的部分不一樣。這時候我們就可以使用模板方法設計模式定義製作骨架,然後部分細節留給子類實現。

代碼實現

首先我們先抽象一個製作飲料的模板,定義演算法邏輯 AbstractBeverage。同時有一個鉤子方法,一般是空實現,在這裡我們可以通過它(customerWantsCondiments())來控制是否加調料。

package com.zero.design.actions.template;

/**
* 抽象製作飲料模板:定義演算法骨架
*/
public abstract class AbstractBeverage {
/**
* 這就是模板方法。它被聲明為final,以免子類改變這個演算法的順序。
* 演算法步驟組合
*/
final void prepareRecipe() {
// 模板方法定義了一連串的步驟,每個步驟由一個方法代表
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}

/**
* 我們在這裡定義了一個方法,(通常)是空的預設實現。這個方法只會返回true,不做別的事。
* 這就是一個鉤子,子類可以覆蓋這個方法,但不見得一定要這麼。
* @return
*/
boolean customerWantsCondiments() {
return true;
}

/**
* 添加佐料:不同飲料也有不同佐料:申明為抽象類,由子類取操心
*/
protected abstract void addCondiments();
/**
* 釀製:不同飲料方式也不同,申明為抽象類,由子類取操心
*/
protected abstract void brew();

/**
* 共通方法:倒入杯中
*/
public void pourInCup() {
System.out.println("倒入杯子中...");
}

/**
* 把水煮沸,共通方法
*/
public void boilWater() {
System.out.println("把水煮沸...");
}
}

接著我們定義泡茶具體演算法,並且繼承 AbstractBeverage 抽象演算法,實現泡茶的具體邏輯。

package com.zero.design.actions.template;

public class Tea extends AbstractBeverage {

/**
* 這樣通過鉤子就可以選擇是都要加佐料了
*/
private boolean addCondiments = true;

/**
* 添加糖、牛奶
*/
@Override
protected void addCondiments() {
System.out.println("添加檸檬,茶更好喝");
}

/**
* 咖啡沖泡方法
*/
@Override
protected void brew() {
System.out.println("秘制泡茶方式放入茶葉");
}

/**
* 使用鉤子,不加佐料
* @return
*/
@Override
boolean customerWantsCondiments() {
return addCondiments;
}

public boolean isAddCondiments() {
return addCondiments;
}

public void setAddCondiments(boolean addCondiments) {
this.addCondiments = addCondiments;
}
}

定義咖啡的演算法細節

package com.zero.design.actions.template;

/**
* 咖啡具體實現:只需要自行處理沖泡和添加調料部分
*/
public class Coffe extends AbstractBeverage {

/**
* 這樣通過鉤子就可以選擇是都要加佐料了
*/
public boolean addCondiments = true;

/**
* 添加糖、牛奶
*/
@Override
protected void addCondiments() {
System.out.println("添加糖跟牛奶");
}

/**
* 咖啡沖泡方法
*/
@Override
protected void brew() {
System.out.println("放入咖啡豆,使用秘制方法沖泡");
}

/**
* 重寫鉤子
* @return
*/
@Override
boolean customerWantsCondiments() {
return addCondiments;
}

public boolean isAddCondiments() {
return addCondiments;
}

public void setAddCondiments(boolean addCondiments) {
this.addCondiments = addCondiments;
}
}

接著就是客戶點單,我們通過模板方法模式製作咖啡或者功夫茶。達到代碼復用。

package com.zero.design.actions.template;

/**
* Created by unique on 2017/6/7.
*/
public class Test {

public static void main(String[] args) {
Tea tea = new Tea();
tea.setAddCondiments(false);
tea.prepareRecipe();
System.out.println("-------------------");
Coffe coffe = new Coffe();
coffe.prepareRecipe();
}

}

輸出如下

把水煮沸...
秘制泡茶方式放入茶葉
倒入杯子中...
-------------------
把水煮沸...
放入咖啡豆,使用秘制方法沖泡
倒入杯子中...
添加糖跟牛奶

模板方法模式的優缺點

優點

1)良好的封裝性。把公有的不變的方法封裝在父類,而子類負責實現具體邏輯。

2)良好的擴展性:增加功能由子類實現基本方法擴展,符合單一職責原則和開閉原則。

3)復用代碼。

缺點

1)由於是通過繼承實現代碼復用來改變演算法,靈活度會降低。

2)子類的執行影響父類的結果,增加代碼閱讀難度。

點贊與收藏是最大的鼓勵。歡迎關注公眾號 JavaStorm


推薦閱讀:
相关文章