在平時開發中,你是否遇到過這種情況:確定了業務邏輯的關鍵步驟及其執行順序,但是某些步驟的具體實現還未知,或者某些步驟的實現與具體的環境有關。
比如,我們去銀行辦理業務的時候,一般都是按照這個步驟來的:取號、排隊等候、辦理業務、評價。這是一個固定的流程,但是其中辦理業務這個步驟是因人而異的,他們可能辦理存款業務、轉賬業務或者是貸款業務。還有報銷的過程,在醫院掛號看病等這些例子,都是有一套固定的流程,但是在某些步驟上有不同的實現。
就像我們平常使用的簡歷模板,論文模板,在模板裡面寫不同的內容。
你看,這不就是我們設計模式中的模板方法模式嘛?
模板方法模式
定義一個操作中的演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
注:這裡的“演算法”,我們可以理解為廣義上的“業務邏輯”,並不特指資料結構和演算法中的“演算法”。這裡的演算法框架就是“模板”,包含演算法框架的方法就是“模板方法”。
-
AbstractClass:抽象類。用來定義演算法框架和抽象操作,具體的子類通過重定義這些抽象操作來實現一個演算法的各個步驟。在這個類裡面,還可以提供演算法中通用的實現。
-
ConcreteClass:具體實現類。用來實現演算法框架中的某些步驟,完成與特定子類相關的功能。
走,我們去銀行看看。
1 public abstract class AbstractClass { 2 // 模板方法(不可以被覆蓋,所以是final) 3 public final void TemplateMethod() { 4 TakeANumber(); // 取號 5 QueueUp(); // 排隊 6 Business(); // 辦理業務 7 Evaluate(); // 評價 8 } 9 10 // 具體方法(也可以設定成抽象方法,推遲到子類實現) 11 protected void TakeANumber() { 12 System.out.println("取號成功!"); 13 } 14 15 protected void QueueUp() { 16 System.out.println("排隊等候!"); 17 } 18 19 protected void Evaluate() { 20 System.out.println("您的業務辦理完成,請您對本次服務做出評價!"); 21 } 22 23 // 特定方法:銀行業務辦理 24 protected abstract void Business(); 25 }
現在有兩個顧客,分別去銀行辦理存款業務和轉賬業務。
1 public class ConcreteClassA extends AbstractClass { 2 protected void Business() { 3 System.out.println("您好,麻煩幫我辦理存款業務!"); 4 } 5 } 6 7 public class ConcreteClassB extends AbstractClass { 8 protected void Business() { 9 System.out.println("您好,麻煩幫我辦理轉賬業務!"); 10 } 11 } 12 public class Demo { 13 public static void main(String[] args) { 14 AbstractClass customerA = new ConcreteClassA(); 15 customerA.TemplateMethod(); 16 17 AbstractClass customerB = new ConcreteClassB(); 18 customerB.TemplateMethod(); 19 } 20 } 21 22 // 客戶A 23 取號成功! 24 排隊等候! 25 您好,麻煩幫我辦理存款業務! 26 您的業務辦理完成,請您對本次服務做出評價! 27 28 // 客戶B 29 取號成功! 30 排隊等候! 31 您好,麻煩幫我辦理轉賬業務! 32 您的業務辦理完成,請您對本次服務做出評價!
抽象模板中的基本方法儘量設計為protected或private型別,符合【迪米特法則】,不需要暴露的屬性或方法儘量不要設定為public。實現類若非必要,儘量不要擴大父類中的訪問許可權。
模板方法模式主要是用來解決複用和擴充套件兩個問題。
模板方法模式把一個演算法中不變的流程抽象到父類的模板方法TemplateMethod()中,將可變的部分Business()留給子類ConcreteClassA和ConcreteClassB來實現,所有的子類都可以複用父類中模板方法定義的流程程式碼。
模板方法模式的第二大作用是擴充套件,這裡所說的擴充套件,並不是指程式碼的擴充套件性,而是指框架的擴充套件性。基於這個作用,模板方法模式常用在框架的開發中,讓框架使用者可以在不修改框架原始碼的情況下,定製化框架的功能。
模板方法模式的優點
-
封裝不變部分,擴充套件可變部分
把認為是不變部分的演算法封裝到父類實現,而可變部分的則可以通過繼承來繼續擴充套件。
-
提取公共部分程式碼,便於維護
-
行為由父類控制,子類實現
基本方法是由子類實現的,因此子類可以通過擴充套件的方式增加相應的功能,符合開-閉原則。
模板方法模式的缺點
-
對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象,間接地增加了系統實現的複雜度。
-
父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它降低了程式碼的可閱讀性。
-
由於繼承關係自身的缺點,如果父類新增新的抽象方法,則所有子類都要改一遍。
模板方法模式的應用場景
1、演算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。
2、當多個子類存在公共的行為時,可以將其提取出來並集中到一個公共父類中以避免程式碼重複。首先,要識別現有程式碼中的不同之處,並且將不同之處分離為新的操作,最後,用一個呼叫這些新的操作的模板方法來替換這些不同的程式碼。
3、當需要控制子類的擴充套件時,模板方法只在特定點呼叫鉤子操作,這樣只允許在這些點進行擴充套件。
模板方法模式在開原始碼中的應用
JDK 中 java.util.AbstractList 抽象集合類,用到了模板方法模式,定義了留給子類實現的 add 方法和模板方法 addAll。
1 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { 2 3 //新增元素的方法,留給子類實現 4 public void add(int index, E element) { 5 throw new UnsupportedOperationException(); 6 } 7 8 //模板方法。新增目標集合類的所有元素,預設呼叫 add 方法實現,也可以被子類重寫 9 public boolean addAll(int index, Collection<? extends E> c) { 10 rangeCheckForAdd(index); 11 boolean modified = false; 12 for (E e : c) { 13 add(index++, e); 14 modified = true; 15 } 16 return modified; 17 } 18 }
AbstractList 每個子類內部的資料結構可能並不相同,對 add 方法的實現延遲到子類,每個子類可以按照自己的邏輯實現。(當然,addAll 方法也可以被覆蓋)
總結
定義一套流程模板,根據需要實現模板中的操作。
參考
極客時間專欄《設計模式之美》