JAVA設計模式什麼鬼(模板方法)——作者:凸凹裡歐
面向物件,是對事物屬性與行為的封裝,方法,指的就是行為。模板方法,顯而易見是說某個方法充當了模板的作用,其充分利用了抽象類虛實結合的特性,虛部抽象預留,實部固定延續,以達到將某種固有行為延續至子類的目的。反觀介面,則達不到這種目的。要搞明白模板方法,首先我們從介面與抽象類的區別切入,這也是面試官經常會問到的問題。
汽車上的介面最常見的就是這幾個了,點菸器,USB,AUX等等,很明顯這些都是介面,它們都預留了某種標準,暴露在系統外部,並與外設對接。就拿點菸器介面來說吧,它原本是專門用於給點菸器供電的,後來由於這個介面在汽車上的通用性,於是衍生出了各種外部裝置,只要是符合這個標準size的,帶正負極簧片的,直流12V的,那就可以使用,比如導航、行車記錄儀、吸塵器什麼的,以及其他各種車載電子裝置。
public interface CigarLighterInterface {//點菸器介面
//供電方法,16V直流電
public void electrifyDC16V();
}
1 public class GPS implements CigarLighterInterface { 2 //導航的實現 3 @Override 4 public void electrifyDC16V() { 5 System.out.println("連線衛星"); 6 System.out.println("定位。。。"); 7 } 8 9 } 1 public class CigarLighter implements CigarLighterInterface { 2 //點菸器的實現 3 @Override 4 public void electrifyDC16V() { 5 int time = 1000; 6 while(--time>0){ 7 System.out.println("加熱電爐絲"); 8 } 9 System.out.println("點菸器彈出"); 10 } 11 12 }
對於點菸器介面來說,它根本不在乎也不知道對接的外設是什麼鬼,它只是定義了一種規範,一種標準,只要符合的都可以對接。再比如USB介面的應用更加廣泛,外設更是應有盡有,具體例子可以參考文章《設計模式是什麼鬼(初探)》。
以上我們可以體會到介面的抽象是淋漓盡致的,實現是空無的,也就是說其方法都是無實現的。然而這樣在某些場景下會存在一些問題,例如有時候我們在父類中只需抽象出一些方法,並且同時也有一些實體方法,以供子類直接繼承,這怎麼辦?答案就是抽象類。舉個例子,哺乳動物類,我們人類就是哺乳動物。
什麼?鯨魚是哺乳類?是的,凡是餵奶的都是哺乳類,不要以為會游泳的都是魚,會飛的都是鳥,蝙蝠同樣是哺乳類,只不過是老鼠中的飛行員而已。
既然如此這哺乳動物肯定是都能餵奶了,但是到底是跑還是遊,或是飛呢還真不好說,但至少可以確認它們都是可以移動的。言歸正傳,我們開始定義哺乳動物抽象類。
1 public abstract class Mammal {
2
3 //既然是哺乳動物當然會餵奶了,但這裡約束為只能母的餵奶
4 protected final void feedMilk(){
5 if(female){//如果是母的……
6 System.out.println("餵奶");
7 }else{//如果是公的……或者可以拋個異常出去。
8 System.out.println("公的不會");
9 }
10 }
11
12 //哺乳動物當然可以移動,但具體怎麼移動還不知道。
13 public abstract void move();
14 }
這裡我們省略了female屬性,其作用是為了控制餵奶行為,大家可以自行新增。可以看到的是,抽象類不同於介面,其自身是可以有具體實現的,也就是說抽象類是虛實結合的,虛部抽象行為,實部遺傳給子類,虛虛實實,飄忽不定。OK,我們看下人、鯨、蝠的子類實現。
public class Human extends Mammal {
@Override
public void move() {
System.out.println("兩條腿走路……");
}
}
public class Whale extends Mammal {
@Override
public void move() {
System.out.println("游泳……");
}
}
public class Bat extends Mammal {
@Override
public void move() {
System.out.println("用翅膀飛……");
}
}
可以看到子類的各路實現都是自己獨有的行為方式,而餵奶那個行為是不需要自己實現的,它是屬於抽象哺乳類的共有行為,哺乳子類不能進行任何干涉。這便是介面與抽象的最大區別了,介面是虛的,抽象類可以有虛有實,介面不在乎實現類是什麼,抽象類會延續其基因給子類。
其實到這裡我們已經說了一大半了,理解了以上部分,剩下的部分就非常簡單了,利用抽象類的這個特性,便有了“模板方法”。舉例說明,我們做軟體專案管理,按瀑布式簡單來講分為:需求分析、設計、編碼、測試、釋出,先不管是用何種方式去實現各個細節,我們就抽象成這五個步驟。
public abstract class PM {
protected abstract void analyze();//需求分析
protected abstract void design();//設計
protected abstract void develop();//開發
protected abstract boolean test();//測試
protected abstract void release();//釋出
}
那麼問題來了,有個程式設計師在需求不明確或者設計不完善的情況下,一上來二話不說直接寫程式碼,這樣就會造成資源的浪費,後期改動太大還會影響專案進度。那麼專案經理這時就應該規範一下這個任務流程,這裡我們加入kickoff()方法實現。
1 public abstract class PM {
2 protected abstract void analyze();//需求分析
3 protected abstract void design();//設計
4 protected abstract void develop();//開發
5 protected abstract boolean test();//測試
6 protected abstract void release();//釋出
7
8 protected final void kickoff(){
9 analyze();
10 design();
11 develop();
12 test();
13 release();
14 }
15 }
這樣就限制了整個專案週期的任務流程,注意這裡要用final宣告此方法子類不可以重寫,只能乖乖的繼承下去用。至於其他的抽象方法,子類可以自由發揮,比如測試方法test(),子類可以用人工測試,自動化測試,我們不care,我們是站在專案管理的抽象高度,只把控流程進度。這裡甚至我們還可以加入一些邏輯如下。
1 public abstract class PM {
2 protected abstract void analyze();//需求分析
3 protected abstract void design();//設計
4 protected abstract void develop();//開發
5 protected abstract boolean test();//測試
6 protected abstract void release();//釋出
7
8 protected final void kickoff(){
9 analyze();
10 design();
11 do {
12 develop();
13 } while (!test());//如果測試失敗,則繼續開發改Bug。
14 release();
15 }
16 }
以下子類只需實現抽象方法,而不用實現固有的模板方法kickoff(),因為它已經被父類PM實現了,並且子類也不能進行重寫。
1 public class AutoTestPM extends PM{
2
3 @Override
4 protected void analyze() {
5 System.out.println("進行業務溝通,需求分析");
6 }
7
8 //design();develop();test();release();實現省略
9 }
至此,我們的模板方法就完成了,抽象類PM中的實方法kickoff()中,以某種邏輯編排呼叫了其他各個抽象方法,提供了一種固定模式的行為方式或是指導方針,以此達到虛實結合、柔中帶剛、剛柔並濟,靈活中不失規範的目的。
當然大部分情況我們使用介面會多於抽象類,因為介面靈活啊,抽象類不允許多繼承啊等等,其實我們還是要看應用場景,在某種無規矩不成方圓,或者規範比較明確,的情況下抽象類的應用是有必要的,世間萬物沒有最好的,只有最合適的。