1. 程式人生 > >設計模式-模板方法模式(Go語言描述)

設計模式-模板方法模式(Go語言描述)

這篇文章我們還是繼續我們的設計模式系列, 今天我們帶來的一個全新的設計模式在實際開發中大家肯定都遇到過, 可能大家只是不知道它叫模板方法模式而已, 今天我們就來詳細的說一下什麼是模板方法模式,已經該模式如何運用.

至於什麼是模板方法模式, 我們還是老規矩, 先來個定義, 然後上張類圖更加直觀的看一下.

定義

模板方法模式定義了一個演算法的步驟,並允許子類別為一個或多個步驟提供其實踐方式。讓子類別在不改變演算法架構的情況下,重新定義演算法中的某些步驟.

這個定義還是非常不錯的, 至少認真讀2遍還是可以理解什麼意思的, 而且我們腦袋裡可以想象到該如何設計這樣的一個結構. 那它有什麼樣的使用場景呢? 其實在定義中已經說的很明白了, 大致有一下兩點應用場景:

  1. 某些類別的演算法中,實做了相同的方法,造成程式碼的重複.
  2. 控制子類別必須遵守的一些事項

上面兩點總結起來說, 可以這樣認為:

系統的元件都是按照一定的流程執行, 並且不同的元件的實現方式不同,需要我們延遲到子類來實現.

模板方法模式的關鍵點還是在將具體操作延遲到子類實現. 接下來, 我們再來看看類圖, 從類圖中我們可以更見直觀的體會一下模板方法模式.

程式碼展示

按照慣例, 在這個環節中我們就應該用go語言來實現模板方法模式了, 不過由於go語言的特殊性, 它並沒有實際意義上的繼承, 所以在go中去實現, 還是和我們上面所說的有點不那麼相同, 所以我決定這裡我們先來用java實現一下模板方法模式, 有了瞭解之後, 我們再來考慮如何用go去實現.在今天這個例子中, 我們打算模擬一下男女生在出門的時候不同行為, 男生在出門之前可能就是在睡覺, 睡醒了爬起來就出去了; 而女生不一樣了, 女生是要打扮一番才能出門的.

java版的模板方法模式

在有了上面的實際例子後, 我們就先來用java模擬一下. 首先我們需要定義一個介面, 用來規範這個行為.

interface IPerson {
    void beforeOut();
    void out();
}

有了介面, 就必須有實現, 不管男生還是女生, 他都是人, 而且在我們這個情景下, 出門這個行為是一樣的, 不一樣的只是出門之前的行為. 對應到程式碼中就是out方法是男女生相同的, 不同的實現是beforeOut方法, 所以我們在設計人這個類的時候之關心out方法的實現.

abstract class Person implements IPerson {
    private
String name; public Person(String name) { this.name = name; } public void out() { beforeOut(); System.out.println("go out...") } }

這裡我們實現了out方法, 並在 out列印之前呼叫了beforeOut方法, 至於這個方法如何實現, 這裡要看是男生還是女生了, 很簡單, 直接來看程式碼.

class Boy extends Person {
    public Boy(String name) {
        super(name);
    }

    public void beforeOut() {
        System.out.println("get up...")
    }
}

class Girl extends Person {
    public Boy(String name) {
        super(name);
    }

    public void beforeOut() {
        System.out.println("dress up...")
    }
}

ok, 不管是男生還是女生, 都繼承自Person這個抽象類, 並且實現了beforeOut這個方法, 只不過男生的這個方法實現是一個get up, 而女生是一個dress up.

現在,一個簡單的模板方法模式的應用我們就完成了, 從程式碼中我們也可以發現, 其實這個模式是很簡單的, 在之前的部落格中我就說過, 設計模式本身都很簡單, 不太容易的是設計模式的應用和實踐.

好了, 在通過java程式碼瞭解模板方法模式之後, 下面開始進入今天的重點了, 如何用go去實現呢?

golang版模板方法模式

對golang瞭解的同學都知道, 其實golang並沒有嚴格意義上的繼承機制, 它僅僅是利用組合的特性來模擬繼承, 在這方法對於組合優於繼承體現的還是很好的, 不過這也帶來了一些問題, 比如不能實現抽象方法, 上面的方法延遲實現等等. 說到這裡大家不要著急, 雖然golang沒有這個特性, 但是我們完全可以換一種思路來完成它, 畢竟模式是死的, 程式碼是活的, 那用什麼方式實現呢? 答案就是繫結! 至於如何運用, 我不多說, 我們直接來看程式碼就行了!

根據上面的java程式碼, 我們來一一的實現它, 首先是介面, 在golang中我們也有介面, 所以, 我們可以設計一個如下的介面.

type IPerson interface {
    SetName(name string)
    BeforeOut()
    Out()
}

這個介面和上面java版的大致相同, 主要的方法都在裡面的, 接下來我們還是要設計一個Person結構體. 這個Person和上面的Person可能不太一樣, 我們先來看程式碼.

type Person struct {
    Specific IPerson
    name     string
}

是有點不一樣, 這裡多了一個IPerson型別的欄位, 這個欄位是幹嘛的? 我們上面提過到, 這裡我們準備用繫結的方式來實現模板方法模式, 這裡的這個特殊的欄位其實就是我們要繫結的例項, 在我們這個例子中就是男生或者女生了, 到這裡可能很多人要迷惑了, 沒關係, 最後我們在看使用的程式碼時就明白了, 這裡我們還是先來看看這個Person有什麼方法吧.

func (this *Person) SetName(name string) {
    this.name = name
}

func (this *Person) Out() {
    this.BeforeOut()
    fmt.Println(this.name + " go out...")
}

func (this *Person) BeforeOut() {
    if this.Specific == nil {
        return
    }

    this.Specific.BeforeOut()
}

第一個方法我們直接無視它, 第二個方法的實現和java版的其實是一樣的, 來看看第三個方法, 這個方法是java版Person沒有去實現的, 按道理將這個BeforeOut是要延遲到子類去實現的, 這個不符合國際慣例啊? 我們還是來看看這個方法的內容吧, 其實關鍵點就一句話, this.Specific.BeforeOut(), 這句話直接呼叫了我們上面說的那個繫結的例項的BeforeOut方法, 仔細品味一下, 還是符合慣例的, 這裡我們只不過用了一種折中的方式來實現方法的延遲實現.

好了, 最難理解的地方我們詳細說了, 下面就是男生和女生的各自實現了, 很簡單, 直接上程式碼.

type Boy struct {
    Person
}

func (_ *Boy) BeforeOut() {
    fmt.Println("get up..")
}

type Girl struct {
    Person
}

func (_ *Girl) BeforeOut() {
    fmt.Println("dress up..")
}

程式碼中,不管是Boy還是Girl都匿名組合了Person結構體, 他們的BeforeOut方法的實現也和上面java版的大體相同, 這裡我們就不再多說了, 最後我們來看看這樣一個設計, 我們該如何使用.

func main() {
    var p *Person = &Person{}

    p.Specific = &Boy{}
    p.SetName("qibin")
    p.Out()

    p.Specific = &Girl{}
    p.SetName("loader")
    p.Out()
}

這裡程式碼的主體還是Person, 只不過我們在區分男女生的時候, 給他指定了Specific欄位, 再來回想一下Person結構體的BeforeOut方法的實現, 是不是瞬間就明白怎麼回事了. 最後我們再來看看執行結果.

最後還是關於文章的例項程式碼的事, 程式碼我都放github上了, 歡迎各種star, https://github.com/qibin0506/go-designpattern