1. 程式人生 > >設計模式-裝飾者模式(Go語言描述)

設計模式-裝飾者模式(Go語言描述)

什麼是裝飾者模式

好久沒有更新設計模式系列的部落格了, 今天我們來聊一聊裝飾者模式, 用過java的同學肯定對裝飾者模式非常熟悉,就算你不知道什麼是裝飾者模式這概念, 你也一定在程式碼中經常用到這個模式,為什麼這麼說呢? 大家都用過java中的流吧, 我們可以這樣寫:

new BufferedOutputStream(new FileOutputStream());

大家對這樣的程式碼肯定很熟悉了, 用另外一個類包裝一下另外一個類, 或方便了我們的使用, 或增強了功能. 不是說設計模式嘛, 怎麼扯開流了… 其實java中這種io操作的程式碼正式裝飾者模式的一種使用.
那它有什麼特點呢?

  1. 理論上它們是可以無限包裝的.
  2. 裝飾者和被裝飾者們有相同的超型別(super).
  3. 想要拓展功能無需修改原有的程式碼, 定義一個裝飾者就可以.

看了這些特點和上面的小段程式碼,不禁讓我們想到了前面說的介面卡模式, 越看越想介面卡模式! 那他們有什麼區別嗎?其實區別很簡單:

介面卡模式是在型別不匹配的時候使用, 目的是將一種型別偽裝成另一種型別以便我們的程式碼可以正常使用;而裝飾者模式裝飾者和被裝飾者擁有相同的型別(相同的超類),目的是為了增強功能或者方便使用.

看完了區別,我們再從上面的程式碼和特點中找一下裝飾者模式都是用了哪些設計原則.

  1. 從”包裝”我們可以看到”多用組合,少用繼承”
  2. 從”拓展”我們可以看到”開閉原則”

在不必改變原類檔案和使用繼承的情況下, 動態地擴充套件一個物件的功能. 它是通過建立一個包裝物件, 也就是裝飾來包裹真實的物件.

概念多看幾遍,對照下面的程式碼理解一下就ok了.扯完了概念,下面我們就應該開始實戰一下, 實現一下這個設計模式了.

程式碼描述環節

在上班的時候, 我和同時經常去路邊吃各種路邊攤, 吃的最多的還是各種面, 有純麵條的, 麵條加雞蛋的, 麵條加火腿腸的… 下面我們就以麵條為例來實現一下程式碼.
首先我們先來定義一個超型別, 也就是一個介面,用來規範麵條的幾個方法.

type Noddles interface
{ Description() string Price() float32 }

只有兩個方法, 一個是麵條的描述,另一個是價格. 接著我們搞一個拉麵出來,

type Ramen struct {
    name  string
    price float32
}

func (p Ramen) Description() string {
    return p.name
}

func (p Ramen) Price() float32 {
    return p.price
}

拉麵有兩個屬性nameprice, 同樣他有兩個方法DescriptionPrice, 所以它實現了上面的Noddles介面. 不著急下面的程式碼,我們先來造一碗麵條嚐嚐.

麵條出來了, 不過光吃麵條不行,我想吃加蛋的麵條, 咋辦? 重寫Ramen讓他支援加蛋嗎? 當然不行, 那以後我們還要加香腸,加西紅柿呢? 難道每次推出新品種都要修改Ramen嗎?
這當然不是一個好辦法, 這個時候我們就可以使用裝飾者模式了. 不就是加個雞蛋嘛, 我們再定義一個雞蛋的裝飾者!

type Egg struct {
    noddles Noddles
    name    string
    price   float32
}

func (p Egg) SetNoddles(noddles Noddles) {
    p.noddles = noddles
}

func (p Egg) Description() string {
    return p.noddles.Description() + "+" + p.name
}

func (p Egg) Price() float32 {
    return p.noddles.Price() + p.price
}

這個雞蛋裝飾者比上面的Ramen多了一個Noddles型別的屬性, 這個屬性也就是我們將要裝飾的型別, 我們下面提供了SetNoddles來設定它. 好了,現在雞蛋裝飾者有了, 可以給我搞一個
加雞蛋的拉麵了吧.

ramen := Ramen{name: "ramen", price: 10}
egg := Egg{noddles: ramen, name: "egg", price: 2}

fmt.Println(egg.Description())
fmt.Println(egg.Price())

我們再初始化Egg的時候只需要指定一下要被裝飾的物件就可以了. 來看看加了雞蛋的拉麵長啥樣.

雞蛋是加上了, 價格也變貴了. 突然有一天我想吃帶香腸的雞蛋拉麵了, 你說咋辦? 很簡單, 依雞蛋畫瓢, 搞出一個香腸的裝飾者來.

type Sausage struct {
    noddles Noddles
    name    string
    price   float32
}

func (p Sausage) SetNoddles(noddles Noddles) {
    p.noddles = noddles
}

func (p Sausage) Description() string {
    return p.noddles.Description() + "+" + p.name
}

func (p Sausage) Price() float32 {
    return p.noddles.Price() + p.price
}

看看我這個加了香腸和雞蛋的拉麵吧.

現在香腸也加上了, 不過價格又高了, 雖然價格貴了點,不過我們終於搞明白什麼是裝飾者模式了, 還是很開心的. 到現在為止, 我們不僅可以吃上香腸雞蛋麵, 還可以吃雙蛋面!

ramen := Ramen{name: "ramen", price: 10}
egg := Egg{noddles: ramen, name: "egg", price: 2}
egg2 := Egg{noddles: egg, name: "egg", price: 2}

fmt.Println(egg2.Description())
fmt.Println(egg2.Price())

突然發現, 現在我可以吃任意組合的面了, 好開心, 你可以試試加4個雞蛋5根香腸啥效果.
總結: 不總結了, 上面扯概念的時候扯的差不多了, 在裝飾者模式中我們再一次見識到了組合的魅力, 所以多用組合,少用繼承.

程式碼放github上了,歡迎star: https://github.com/qibin0506/go-designpattern