【java設計模式】之 模板方法(Template Method)模式
1. 模板方法的一個例項
這一節主要來學習一下設計模式中的模板方法模式。我們先來看一個例子:假如現在老闆讓你做一個汽車的模型,要求只要完成基本功能即可,不考慮擴充套件性,那你會怎麼做呢?我們首先會根據經驗設計一個類圖:
由這個類圖可知,非常簡單的實現了悍馬車,該車有兩個型號H1和H2。那現在我們開始實現這兩個型號的悍馬車,首先我們得把抽象類寫好,然後兩個不同的模型實現類通過簡單的繼承就可以實現要求。首先看看抽象類的程式碼:
public abstract class HummerModel { public abstract void start(); //發動 public abstract void stop(); //停止 public abstract void alarm(); //鳴笛 public abstract void engineBoom(); //轟鳴 public abstract void run(); //車總歸要跑 }
簡單到不行,下面我們來實現兩個悍馬的模型:
//悍馬H1 public class HummerH1 implements HummerModel { @Override public void start() { System.out.println("H1發動……"); } @Override public void stop() { System.out.println("H1停止……"); } @Override public void alarm() { System.out.println("H1鳴笛……"); } @Override public void engineBoom() { System.out.println("H1轟鳴……"); } @Override public void run() { this.start(); this.engineBoom(); this.alarm(); this.stop(); } } //悍馬H2 public class HummerH2 implements HummerModel { @Override public void start() { System.out.println("H2發動……"); } @Override public void stop() { System.out.println("H2停止……"); } @Override public void alarm() { System.out.println("H2鳴笛……"); } @Override public void engineBoom() { System.out.println("H2轟鳴……"); } @Override public void run() { this.start(); this.engineBoom(); this.alarm(); this.stop(); } }
很明顯,已經發現程式碼有點問題了,兩個悍馬的run方法完全相同。所以這個run方法應該出現在抽象類中,不應該在實現類中,抽象是所有子類的共性封裝。所以我們修改一下抽象類:
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(); } }
這樣兩個實現類就不用實現run方法了,可以直接拿來用。其實,這就是模板方法模式。
2. 模板方法模式的定義
模板方法模式很簡單,它的定義是:Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses define certain steps of an algorithm without changing the algorithm's structure. 即定義一個操作中的演算法框架,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構即可衝定義該演算法的某些特定步驟。模板方法模式的通用類圖如下:
模板方法模式確實很簡單,僅僅使用了java的繼承機制,但是它是一個應用非常廣泛的模式,其中AbstractClass叫做抽象模板,它的方法分為兩類:基本方法(由子類去實現)和模板方法(可以有一個或多個,也就是一個框架,實現對基本方法的排程,完成固定的邏輯)。為了防止惡意的操作,一般模板方法上都新增上final關鍵字,不允許被覆寫。我們來看一下AbstractClass模板:
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
protected abstract void doAnything();
//模板方法
public void templateMethod() {
//呼叫基本方法,完成相關的邏輯
this.doAnything();
this.doSomething();
}
}
具體實現類就不寫了……
3. 模板方法模式的優缺點
優點:
1)封裝不變部分,擴充套件可變部分:把認為不變部分的演算法封裝到父類實現,可變部分則可以通過繼承來實現,很容易擴充套件。
2)提取公共部分程式碼,便於維護:上面悍馬的例子就是個很好的解釋。
3)行為由父類控制,由子類實現。
缺點:
模板方法模式顛倒了我們平常的設計習慣:抽象類負責宣告最抽象、最一般的事物屬性和方法,實現類實現具體的事物屬性和方法。在複雜的專案中可能會帶來程式碼閱讀的難度。
4. 模板方法模式的擴充套件
還是上面那個悍馬的例子,現在老闆說這車幹嘛跑起來就要鳴笛,太吵了,難道不是應該讓使用者決定它是否要鳴笛麼?好像確實是這樣的……那好辦,我們可以修改一下抽象模板類中的方法:
public abstract class HummerModel {
protected abstract void start(); //發動
protected abstract void stop(); //停止
protected abstract void alarm(); //鳴笛
protected abstract void engineBoom(); //轟鳴
final public void run() { //車總歸要跑
this.start();
this.engineBoom();
if(this.isAlarm()) {//想讓它叫就叫,不想就不叫
this.alarm();
}
this.stop();
}
protected boolean isAlarm() { //我們加了一個判斷方法,預設返回true
return true;
}
}
我們在模板類中增加了一個判斷方法來判斷是否要鳴笛,現在就好辦了,具體實現類只要重寫這個方法就可以做到人為控制是否要鳴笛了,下面我們來看一下實現類:
public class HummerH1 extends HummerModel {
private boolean alarmFlag = true; //判斷標記
@Override
public void start() {
System.out.println("H1發動……");
}
@Override
public void stop() {
System.out.println("H1停止……");
}
@Override
public void alarm() {
System.out.println("H1鳴笛……");
}
@Override
public void engineBoom() {
System.out.println("H1轟鳴……");
}
@Override
protected boolean isAlarm() { //覆寫isAlarm方法,返回判斷標記
return this.alarmFlag;
}
public void setAlarm(boolean isAlarm) { //設定判斷標記
this.alarmFlag = isAlarm;
}
}
這個實現很好,我們在實現類中定義一個判斷標記,然後對外提供一個public介面setAlarm來讓外界設定這個判斷標記,這就像是開關一樣,想讓它ture和false都行。這個isAlarm方法俗稱鉤子方法。有了鉤子方法的模板方法模式才算完美,大家可以想象一下,由子類的一個方法返回值決定公共部分的執行結果,這個是很有吸引力的。我們來測試一下:
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("----H1型悍馬----");
System.out.println("是否需要喇叭聲響? 0-不需要 1-需要");
String type = new BufferedReader(new InputStreamReader(System.in)).readLine();
HummerH1 h1 = new HummerH1();
if(type.equals("0")) {
h1.setAlarm(false);
}
h1.run();
}
}
當輸入不同的指令後,就會決定不同的動作:即要不要鳴笛,至此,這個模板方法模式就介紹完了。
文末福利:“程式設計師私房菜”,一個有溫度的公眾號~
_____________________________________________________________________________________________________________________________________________________
-----樂於分享,共同進步!