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

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

什麼是裝飾者模式

好久沒有更新設計模式系列的部落格了, 今天我們來聊一聊裝飾者模式, 用過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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.