模板方法模式(Template Method Pattern)。
定義:
定義一個操作中的演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
注意:為了防止惡意的操作,一般模板方法都加上final關鍵字,不允許被覆寫。
通用程式碼:
抽象模板類
它的方法分為兩類:
- 基本方法
基本方法也叫作基本操作,是由子類實現的方法,並且在模板方法被呼叫。
- 模板方法
可以有一個或幾個,一般是一個具體方法,也就是一個框架,實現對基本方法的排程,完成固定的邏輯。
public abstract class AbstractClass { // 基本方法 protected abstract void doSomething(); // 基本方法 protected abstract void doAnything(); // 模板方法 public void templateMethod(){ // 呼叫基本方法,完成相關的邏輯 this.doAnything(); this.doSomething(); } }
具體模板類
public class ContreteClass1 extends AbstractClass {
// 實現基本方法
protected void doAnything(){
// 業務邏輯處理
}
protected void doSomething(){
// 業務邏輯處理
}
}
場景類
public class Client { public static void main(String[] args) { AbstractClass class1 = new ConcreteClass1(); AbstrcatClass class2 = new ConcreteClass2(); // 呼叫模板方法 class1.templateMethod(); class2.templateMethod(); } }
注意:抽象模板中的基本方法儘量設計為protected型別,符合迪米特法則,不需要暴露或方法儘量不要設定為protected型別。實現類若非必要,儘量不要擴大父類中的訪問許可權。
優點:
- 封裝不變部分,擴充套件可變部分
把認為是不變部分的演算法封裝到父類實現,而可變部分的則可以通過繼承來繼續擴充套件。
- 提取公共部分程式碼,便於維護
- 行為由父類控制,子類實現
基本方法是由子類實現的,因此子類可以通過擴充套件的方式增加相應的功能,符合開閉原則。
缺點:
按照我們的設計習慣,抽象類負責宣告最抽象,最一般的事物屬性和方法,實現類完成具體的事物屬性和方法。但是模板方法模式卻顛倒了,抽象類定義了部分抽象方法, 由子類實現,子類執行的結果影響了父類的結果,也就是子類對父類產生了影響,這在複雜的專案中,會帶來程式碼閱讀的難度,而且也會讓新手產生不適感。
使用場景:
- 多個子類有共有的方法,並且邏輯基本相同時。
- 重複、複雜的演算法,可以把核心演算法設計為模板方法,周邊的相關細節功能則由各個子類實現。
- 重構時,模板方法模式時一個經常使用的模式,把相同的程式碼抽取到父類中,然後通過鉤子函式(見“模板方法模式的擴充套件”)約束其行為。
案例程式碼:
抽象悍馬模型
public abstract class HummerModel {
// 首先,這個模型要能夠被髮動起來,別管是手搖發動,還是電力發動,反正是要能夠發動起來,那這個實現要在實現類裡了
public abstract void start();
// 能發動,還要能停下來,那才是真本事
public abstract void stop();
// 喇叭會出聲音,是滴滴叫,還是嗶嗶叫
public abstract void alarm();
// 引擎會轟隆隆地響,不響那是假的
public abstract void engineBoom();
// 那模型應該會跑吧,別管是人推,還是電力驅動的,總之要會跑
public void run() {
//先發動車
this.start();
// 引擎開始轟鳴
this.engineBoom();
// 然後就開始跑了,跑得過程中遇到一條狗擋道,就按喇叭
this.alarm();
// 到達目的地就停車
this.stop();
}
}
H1型號悍馬模型(H2型悍馬模型程式碼與其一致,省略)
public class HummerHiModel extends HummerModel {
// H1型號的悍馬車鳴笛
public void alarm() {
System.out.println("悍馬H1鳴笛...");
}
// 引擎轟鳴聲
public void engineBoom() {
System.out.println("悍馬H1引擎聲音是這樣的...");
}
// 汽車發動
public void start() {
System.out.println("悍馬H1發動...");
}
// 停車
public void stop() {
System.out.println("悍馬H1停車...");
}
}
注意:在軟體開發過程中,如果相同的一段程式碼複製過兩次,就需要對設計產生懷疑,架構師要明確地說明為什麼相同的邏輯要出現兩次或更多次。
場景類
public class Client {
public static void main(String[] args) {
// XX公司要H1型號的悍馬
HummerModel h1 = new HummerH1Model();
// H1模型演示
h1.run();
}
}
模板方式的擴充套件:
環境模擬:
H1型號的悍馬喇叭想讓它響就響,H2型號的喇叭不要有聲音。
擴充套件後的抽象模板類
public abstract class HummerModel {
// 首先,這個模型要能夠被髮動起來,別管是手搖發動,還是電力發動,反正是要能夠發動起來,那這個實現要在實現類裡了
public abstract void start();
// 能發動,還要能停下來,那才是真本事
public abstract void stop();
// 喇叭會出聲音,是滴滴叫,還是嗶嗶叫
public abstract void alarm();
// 引擎會轟隆隆地響,不響那是假的
public abstract void engineBoom();
// 那模型應該會跑吧,別管是人推,還是電力驅動的,總之要會跑
public void run() {
//先發動車
this.start();
// 引擎開始轟鳴
this.engineBoom();
// 然後就開始跑了,跑得過程中遇到一條狗擋道,就按喇叭
if(this.isAlarm()) {
this.alarm();
}
// 到達目的地就停車
this.stop();
}
// 鉤子方法,預設喇叭是會想的
protected boolean isAlarm() {
return true;
}
}
isAlarm()是一個實現方法。其作用是模板方法根據其返回值決定是否要響喇叭,子類可以覆寫該方法值。
擴充套件後的H1悍馬
public class HummerHiModel extends HummerModel {
private boolean alarmFlag = true; // 要響喇叭
// H1型號的悍馬車鳴笛
public void alarm() {
System.out.println("悍馬H1鳴笛...");
}
// 引擎轟鳴聲
public void engineBoom() {
System.out.println("悍馬H1引擎聲音是這樣的...");
}
// 汽車發動
public void start() {
System.out.println("悍馬H1發動...");
}
// 停車
public void stop() {
System.out.println("悍馬H1停車...");
}
protected boolean isAlarm() {
reuturn this.alarmFlag;
}
// 要不要響喇叭,是由客戶來決定的
public void setAlarm(boolean isAlarm) {
this.alarmFlag = isAlarm;
}
}
擴充套件後的H2悍馬
public class Hummer2HiModel extends HummerModel {
private boolean alarmFlag = true; // 要響喇叭
// H1型號的悍馬車鳴笛
public void alarm() {
System.out.println("悍馬H1鳴笛...");
}
// 引擎轟鳴聲
public void engineBoom() {
System.out.println("悍馬H1引擎聲音是這樣的...");
}
// 汽車發動
public void start() {
System.out.println("悍馬H1發動...");
}
// 停車
public void stop() {
System.out.println("悍馬H1停車...");
}
// 預設沒有喇叭的
protected boolean isAlarm() {
reuturn false;
}
}
在我們的抽象類中isAlarm的返回值就是影響了模板方法的執行結果,該方法就叫做鉤子方法(Hook Method)。
模板方法模式就是在模板方法中按照一定的規則和順序呼叫基本方法,具體到前面那個例子,就是run()方法按照規定的順序呼叫本類的其他方法,並且由isAlarm()方法的返回值確定run()中的執行順序變更。
最佳實踐:
父類是否可以呼叫子類的方法呢?回答是能,但強烈的、極度的不建議這麼做,那該怎麼做呢?
- 把子類傳遞到父類的有參構造中,然後呼叫。
- 使用反射的方式呼叫,你使用了反射還有誰不能呼叫的?
- 父類呼叫子類的靜態方法。
父類建立框架,子類在重寫了父類部分的方法後,再呼叫從父類繼承的方法,產生不同的結果(而這正是模板方法模式)。這是不是也可以理解為父類呼叫了子類的方法呢?你修改了子類,影響了父類行為的結果,曲線救國的方式實現了父類依賴子類的場景,模板方法模式就是這種效果。
在《xxx In Action》中就說明了,如果你需要擴充套件功能,可以繼承這個抽象類,然後覆寫protected方法,再然後就是呼叫一個類似execute方法,就完成你的擴充套件開發,非常容易擴充套件的一種模式。