本文微信公眾號「AndroidTraveler」首發。 背景 最近在看《設計模式之禪》,為了能夠更加深入的理解設計模式,達到學以致用。 這邊記錄一下自己的一些感受和看法,並結合具體代碼實戰來進行說明。 模板方法模式 但凡和設計模式掛上鉤,我們總是會覺得「高不可攀」。 然而實際上,設計模式是基於大量實 ...
本文微信公眾號「AndroidTraveler」首發。
背景
最近在看《設計模式之禪》,為了能夠更加深入的理解設計模式,達到學以致用。
這邊記錄一下自己的一些感受和看法,並結合具體代碼實戰來進行說明。
模板方法模式
但凡和設計模式掛上鉤,我們總是會覺得「高不可攀」。
然而實際上,設計模式是基於大量實際代碼的經驗總結,它來自於實際的代碼。
與其說「高不可攀」,其實它反而是比較「接地氣」。
而模板方法模式相信你看完本篇文章之後,會發現,原來這就是模板方法模式,然後就去看你之前的代碼了。
小例子初識模板方法模式
理解設計模式最好的方法就是通過項目開發中的實際場景來說明。
大家做 Android 開發的時候寫 Activity 應該都會看到下麵類似代碼吧?
private void getIntents() {
// 從 Intent 獲取傳遞過來的一些參數,設置到屬性中
}
private void findViewById() {
// 通過 findViewById 來初始化各個組件
}
private void setViews() {
// 給組件設置監聽或者初始狀態
}
假設我每個界面都這樣寫,那麼重覆代碼太多了,很沒必要。
雖然每個方法具體的邏輯不一樣,但是都有這些操作。
這個時候我們第一個想法就是繼承,抽取出一個 BaseActivity。
然後將這些通用代碼都放到了 BaseActivity 裡面,子類再來覆寫就可以了。
但是還有一個問題,那就是,我每次都需要寫下麵代碼:
getIntents();
findViewById();
setViews();
尤其是通用代碼多的時候,有時候手誤可能導致某些界面這三個方法調用順序還不一樣。
那怎麼辦呢?我們可以抽取出一個方法,這個方法代表了這三個方法統一的調用順序,這樣就不怕手誤寫錯了。
而這個方法就是我們的模板方法。
public abstract class BaseActivity extends Activity {
/**
* 從 Intent 獲取傳遞過來的一些參數,設置到屬性中
*/
protected abstract void getIntents();
/**
* 通過 findViewById 來初始化各個組件
*/
protected abstract void findViewById();
/**
* 給組件設置監聽或者初始狀態
*/
protected abstract void setViews();
/**
* 模板方法
*/
final public void init() {
getIntents();
findViewById();
setViews();
}
}
這樣我後面的 Activity 都可以繼承這個 BaseActivity,然後只需要調用 init 方法即可。
至於不同的 Activity 的邏輯我再在三個方法裡面各自實現即可。
鉤子方法
一聽到這個詞,是不是覺得有點「高大上」,似乎很 NB?
然而,聽完我後面的講述,你內心估計
我們上面的方法裡面,並不是所有的 Activity 都有其他 Activity 傳遞數據過來的,因此 getIntents 這個方法不一定所有子類都要調用。
這個時候我們可以提供一個鉤子方法,改動部分代碼如下:
/**
* 模板方法
*/
final public void init() {
if (isGetIntents()) {
getIntents();
}
findViewById();
setViews();
}
/**
* 鉤子方法,是否需要設置數據,預設為 true
* @return
*/
protected boolean isGetIntents() {
return true;
}
可以看到,通過鉤子方法,當我們預設需要獲取數據時,什麼都不用改動,如果我們不需要獲取數據,只需要覆寫我們的鉤子方法 isGetIntents 並返回 false 即可。
好了,有了初步的印象之後,接下來就正式的加深瞭解吧。
定義
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
定義一個操作中的演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
簡單的說就是父類定義了一個模板方法,在這個模板方法裡面有一些特定的步驟。具體的步驟實現留給子類去處理。
父類的模板方法保持了各個子類的共性,模板方法裡面的步驟使得每個子類都有自己的個性。
通用代碼實現
父類:
public abstract class AbstractPatternClass {
/**
* 基本方法,模板方法裡面調用
*/
protected abstract void firstModule();
/**
* 基本方法,模板方法裡面調用
*/
protected abstract void secondModule();
/**
* 模板方法,多個基本方法組合
*/
final public void templateMethod() {
firstModule();
secondModule();
}
}
具體子類:
public class ConcreteClass extends AbstractPatternClass {
@Override
protected void firstModule() {
// TODO 子類實現自己的邏輯
}
@Override
protected void secondModule() {
// TODO 子類實現自己的邏輯
}
}
場景使用類:
public class PatternTest {
public static void main(String[] args) {
AbstractPatternClass abstractPatternClass = new ConcreteClass();
// 調用模板方法
abstractPatternClass.templateMethod();
}
}
鉤子方法我們上面已經說過了,相信聰明的你知道如何使用,這裡就不再贅述了。
註意點
父類中的基本方法儘量設計為 protected 類型,符合迪米特法則。
父類中的模板方法一般設置為 final,不允許子類覆寫。這樣的目的一個是為了避免子類惡意操作,一個是為了模板的共性。
應用
當你在寫代碼經常用到複製和粘貼快捷鍵時,你就要考慮是不是可以進行抽取。
當你修改一個地方的時候,發現其他地方也要連帶修改,也需要考慮一下。
多個子類有公共方法,並且邏輯基本相同。
複雜的一些演算法之類的,可以讓子類通過基本方法傳遞一些參數,核心邏輯放在模板方法裡面。
重構項目的時候,也可以考慮一下把相同代碼抽取到父類,通過鉤子方法定製化模板。
結語
最後一點就是註意不要濫用設計模式,不要為了設計而設計
參考資料:
設計模式之禪(第2版)