設計模式-模板方法模式

說到模板方法模式,它多是一個讓咱們深刻骨髓而又不自知的模式了,由於它在咱們開發過程當中會常常遇到,而且也很是簡單。只不過,不少時候咱們並不知道它就是模板方法模式而已。不負責任的說,當咱們用到override關鍵字重寫父類方法的時候,十有八九就跟模板方法模式有關了。git

定義

先看一下模板方法模式的定義,模板方法模式定義了一個操做中的算法的框架,而將一些步驟延遲到子類中。使得子類能夠不改變一個算法的結構便可重定義該算法的某些步驟。github

這裏延遲到子類說的玄乎,其實就是子類繼承並實現父類中的抽象方法(abstract),而重定義該算法的某些步驟指的就是子類重寫父類的虛方法(virtual)。不過,不論是哪個,子類都須要用到override算法

實例

咱們仍是經過一個例子來解釋模板方法模式,先來一個經典的腦筋急轉彎。框架

把一個大象裝進冰箱要幾個步驟?

答案是三步:
- 第一步,把冰箱門打開
- 第二步,把大象放進去
- 第三步,把冰箱門關上ide

對應到前面的定義,這裏把大象裝進冰箱的步驟就是算法的框架,而其中的每一步就是算法的具體步驟。咱們用代碼實現看看:函數

public abstract class AnimalToFridge
{
    public void Do()
    {
        OpenFridge();

        PutIntoFridge();

        CloseFridge();
    }

    private void OpenFridge()
    {
        Console.WriteLine("把冰箱門打開");
    }

    public abstract void PutIntoFridge();

    private void CloseFridge()
    {
        Console.WriteLine("把冰箱門關上");
    }
}

上面定義了一個把動物放進冰箱的基類,Do()方法定義了把大象裝進冰箱的算法骨架,其中,打開冰箱和關閉冰箱兩個步驟是固定不變的,變化只是把什麼動物放進去。學習

再定義一個把大象放冰箱的子類,繼承自上面的基類:設計

public class ElephantToFridge:AnimalToFridge
{
    public override void PutIntoFridge()
    {
        Console.WriteLine("把大象放進去");
    }
}

使用時,咱們只須要調用Do()方法就能夠完成把大象放冰箱的動做了:code

static void Main(string[] args)
{
    AnimalToFridge elephantToFridge = new ElephantToFridge();
    elephantToFridge.Do();
}

這時候若是咱們要把其餘動物放進去,只須要繼承AnimalToFridge就能夠了,例如,咱們把狗放進冰箱:對象

public class DogToFridge: AnimalToFridge
{
    public override void PutIntoFridge()
    {
        Console.WriteLine("把狗放進去");
    }
}

可是你覺得這麼簡單就結束了嗎?知道這個腦筋急轉的朋友應該都知道它還有第二問。

而後把一個長頸鹿裝進冰箱要幾個步驟?

答案是四步:
- 第一步,把冰箱門打開
- 第二步,把大象弄出來
- 第三步,把長頸鹿放進去
- 第四步,把冰箱門關上

咱們能夠分析一下需求,也就是說,把大象放進以前不須要先把什麼拿出來,可是放長頸鹿須要先把大象弄出來。再進一步分析的話,能夠推測把雞蛋、螞蟻這樣的小東西放進去,即便裏面有大象,應該也不須要先把大象拿出來,而放獅子、老虎這樣的大型動物就須要清空冰箱。爲了知足這樣的需求,咱們的虛方法就登場了,代碼能夠作以下改進:

public abstract class AnimalToFridge
{
    public void Do()
    {
        OpenFridge();

        BeforePutIntoFridge();

        PutIntoFridge();

        CloseFridge();
    }

    private void OpenFridge()
    {
        Console.WriteLine("把冰箱門打開");
    }

    protected virtual void BeforePutIntoFridge() { }

    protected abstract void PutIntoFridge();

    private void CloseFridge()
    {
        Console.WriteLine("把冰箱門關上");
    }
}

基類中增長了一個BeforePutIntoFridge()的虛方法,方法只有一個空的實現(固然,若是須要的話,也能夠添加具體內容),除此以外,我把虛方法和抽象方法的訪問修飾符都改爲protected了,由於,算法的單個步驟不該該被客戶端直接調用,調用了也沒有任何意義。這樣,咱們的大象和長頸鹿子類就能夠以下實現了:

public class ElephantToFridge : AnimalToFridge
{
    protected override void PutIntoFridge()
    {
        Console.WriteLine("把大象放進去");
    }
}

public class GiraffeToFridge : AnimalToFridge
{
    protected override void BeforePutIntoFridge()
    {
        Console.WriteLine("把大象弄出來");
    }

    protected override void PutIntoFridge()
    {
        Console.WriteLine("把長頸鹿放進去");
    }
}

ElephantToFridge類不重寫父類的BeforePutIntoFridge()方法,而GiraffeToFridge類重寫了,也就是定義中所說的重定義了該算法的某些步驟了。

好了,這樣就改造完成並知足需求了,咱們再來看一下最終的總體類圖:

這就是模板方法模式,其實就是對繼承加抽象方法和虛方法的使用,這可能算是繼承的巔峯時刻了吧,在其餘模式中只有被吐槽的命。

UML類圖

再抽象一下就能夠獲得模板方法模式的UML類圖了:

鉤子函數

在學習模板方法模式的時候,咱們可能會常常聽到鉤子函數這個概念。鉤子就是給子類一個受權,讓子類來可重定義模板方法的某些步驟,聽着高大上,說白了就是虛方法而已。

優缺點

優勢

  • 封裝了算法骨架,提升了代碼複用性,簡化了使用難度;
  • 封裝不變部分,擴展可變部分,知足開閉原則。

缺點

  • 算法骨架不易更改,也就是原先定義的算法步驟若是須要變化,就不得不修改源代碼了;
  • 擴展時,可能會產生不少子類,這是繼承不可避免的缺陷。

跟建造者模式的異同

建造者模式不少地方跟模板方法模式是很類似的,例如,他們都是經過繼承實現,都會把易變化的部分延遲到子類實現,而且都有一個方法封裝骨架,只不過,建造者模式延遲到子類的是各部件的建立,封裝的是最後的構建流程。而模板方法模式延遲到子類實現的是算法的某些步驟,封裝的是算法骨架。也就是說若是你認可建立對象也是一種算法的話,那兩者其實就差很少了。不過呢?他們也是有區別的,由於建造者模式中,各部件的建造須要客戶端配合完成,所以,建造各部件的方法須要是public的,而模板方法模式中,各單獨的算法步驟不該該被客戶端直接調用,所以一般是protected的。不過,儘管如此,他們的設計思想確實是大同小異的。

說到這裏,還記得建造者模式是如何經過使用委託來緩解子類過多的問題的嗎?既然模板方法模式與建造者模式類似,那麼處理方式也應該類似了,咱們看看最終實現效果:

public class AnimalToFridge
{
    public void Do(Action beforePutIntoFridge,Action putIntoFridge)
    {
        OpenFridge();

        beforePutIntoFridge?.Invoke();

        putIntoFridge?.Invoke();

        CloseFridge();
    }

    private void OpenFridge()
    {
        Console.WriteLine("把冰箱門打開");
    }

    private void CloseFridge()
    {
        Console.WriteLine("把冰箱門關上");
    }
}

直接把抽象方法和虛方法都去掉了,換成了委託,只保留了算法骨架。這樣作好處很明顯,不須要子類了,不管多少動物,所有都經過委託搞定了。不過缺點也很明顯,算法的實現交給了客戶端,給客戶端的使用帶來了不小的負擔,而且若是調用位置不少,還會致使大量代碼重複,難以維護。

所以,模板方法模式具體該如何使用還得視狀況而定。

源碼連接

相關文章
相關標籤/搜索