天天看點

設計模式是什麼鬼(模闆方法)

面向對象,是對事物屬性與行為的封裝,方法,指的就是行為。模闆方法,顯而易見是說某個方法充當了模闆的作用,其充分利用了抽象類虛實結合的特性,虛部抽象預留,實部固定延續,以達到将某種固有行為延續至子類的目的。反觀接口,則達不到這種目的。要搞明白模闆方法,首先我們從接口與抽象類的差別切入,這也是面試官經常會問到的問題。

設計模式是什麼鬼(模闆方法)
汽車上的接口最常見的就是這幾個了,點煙器,USB,AUX等等,很明顯這些都是接口,它們都預留了某種标準,暴露在系統外部,并與外設對接。就拿點煙器接口來說吧,它原本是專門用于給點煙器供電的,後來由于這個接口在汽車上的通用性,于是衍生出了各種外部裝置,隻要是符合這個标準size的,帶正負極簧片的,直流12V的,那就可以使用,比如導航、行車記錄儀、吸塵器什麼的,以及其他各種車載電子裝置。

public interface CigarLighterInterface {//點煙器接口
    //供電方法,16V直流電
    public void electrifyDC16V();
}
           
1public class GPS implements CigarLighterInterface {
2    //導航的實作
3    @Override
4    public void electrifyDC16V() {
5        System.out.println("連接配接衛星");
6        System.out.println("定位。。。");
7    }
8
9}
           
1public 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接口的應用更加廣泛,外設更是應有盡有,具體例子可以參考文章《設計模式是什麼鬼(初探)》。

以上我們可以體會到接口的抽象是淋漓盡緻的,實作是空無的,也就是說其方法都是無實作的。然而這樣在某些場景下會存在一些問題,例如有時候我們在父類中隻需抽象出一些方法,并且同時也有一些實體方法,以供子類直接繼承,這怎麼辦?答案就是抽象類。舉個例子,哺乳動物類,我們人類就是哺乳動物。

設計模式是什麼鬼(模闆方法)

什麼?鲸魚是哺乳類?是的,凡是喂奶的都是哺乳類,不要以為會遊泳的都是魚,會飛的都是鳥,蝙蝠同樣是哺乳類,隻不過是老鼠中的飛行員而已。

設計模式是什麼鬼(模闆方法)

既然如此這哺乳動物肯定是都能喂奶了,但是到底是跑還是遊,或是飛呢還真不好說,但至少可以确認它們都是可以移動的。言歸正傳,我們開始定義哺乳動物抽象類。

1public 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()方法實作。

1public 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,我們是站在項目管理的抽象高度,隻把控流程進度。這裡甚至我們還可以加入一些邏輯如下。

1public 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實作了,并且子類也不能進行重寫。

1public 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()中,以某種邏輯編排調用了其他各個抽象方法,提供了一種固定模式的行為方式或是指導方針,以此達到虛實結合、柔中帶剛、剛柔并濟,靈活中不失規範的目的。

設計模式是什麼鬼(模闆方法)

當然大部分情況我們使用接口會多于抽象類,因為接口靈活啊,抽象類不允許多繼承啊等等,其實我們還是要看應用場景,在某種無規矩不成方圓,或者規範比較明确,的情況下抽象類的應用是有必要的,世間萬物沒有最好的,隻有最合适的。