Golang 實現設計模式 —— 裝飾模式

概念


「用於代替繼承的技術,無需經過繼承增長子類就能擴展對象的新功能」html

「動態地給一個對象添加一些額外的職責,就增長功能來講,裝飾模式比生成子類更爲靈活」git

什麼時候用


須要擴展一個類的功能,或給一個類增長附加責任github

須要動態的給一個對象增長功能,且能夠動態地撤銷它golang

須要增長一些基本功能的排列組合而產生的大量的功能,而使得繼承變得很是困難的時候函數

實現構件


抽象構件(Component) 表示「被」裝飾的本體的抽象定義,這個定義一般是一個接口(Interface),定義了若干方法(能力),這些方法能夠用來在被具體裝飾角色(ConcreteDecorator)實現時改變原有構件本體的方法(能力),如原來本體傷害輸出是 10,裝飾角色把它再增長 10 而不會影響本體的原有邏輯(代碼)。spa

具體構件(ConcreteComponent) 表示實現了抽象構件(Component)的對象,即將要接受附加能力的對象,本文中比喻的「本體」。設計

抽象裝飾(Decorator) 持有一個抽象構件的實例(即具體構件),並定義(實現)與抽象構件接口一致的接口。抽象裝飾的(部分)做用應該是應用了依賴倒置原則,在結構上使具體裝飾(ConcreteDecorator)不要直接依賴於抽象構件,由於兩者做用性質也不一樣,直接依賴灰常奇怪,就好像都是50歲的男子也不能把隔壁老王當成爸爸同樣。指針

具體裝飾(ConcreteDecorator) 實現了抽象裝飾的定義,負責給構件對象添加附加能力的對象。具體裝飾經過實現抽象裝飾定義的接口,擁有了和具體構件同樣的「能力」(方法/函數/屬性),再經過抽象裝飾定義中所持有的抽象構件的實例而得到對該實例「相同」能力的結果,並在結果上進行一些裝飾。code

實現步驟


  • 定義抽象構件,提供抽象接口
  • 定義具體構件並實現抽象構件,構造後的具體構件即理解爲「本體」,被裝飾的對象
  • 定義抽象裝飾,它要作兩件事,實現抽象構件和保存一個抽象構件對象
  • 定義具體裝飾,具體裝飾要實現抽象裝飾,並在實現的接口方法中對構件進行具體裝飾操做
  • 以後,要增長「本體」就建立具體構件,要增長裝飾物,就建立具體裝飾
  • 使用時,把本體「傳遞進」裝飾對象,在裝飾對象(一樣繼承自抽象構件)的方法裏去使用本體的方法和結果,加工它,並輸出進行了「調整」的結果

原理與代碼


用 Golang 描述代碼結構(代碼模仿自 github,但很差意思忘記來自哪位做者了)component

package component

/*
抽象構件(Component)接口
 */
type Beverage interface {
	// 計算價格
	Cost() int
	// 返回描述
	Me() string
}

上面定義了抽象構件接口「飲料」接口,它包含了兩個方法,輸出價格和描述本身,飲料接口做爲最底層接口是全部飲料都必需要實現的(能力)。

package component

type Tea struct {
    Beverage // 做用?
	name string
	price int
}

func (self *Tea) Me() string {
	return self.name
}

func (self *Tea) Cost() int {
	return self.price
}

有了飲料這個概念,就在此基礎之上建立第一款具體的產品:「茶」。茶是飲料,所以它要繼承飲料的特性(實現接口)。如何表達茶實現了飲料接口,使得上層調用茶時能夠訪問茶的接口呢?按照 Golang 的語法特性先定義一個 Tea 結構(類),先有了茶。

Golang 中實現接口無需聲明,實現該接口全部方法即爲(自動)實現接口,所以 Tea 類要經過實現 MeCost 兩個具體方法來實現對接口的實現(這話說的)。與普通方法不一樣的是在 funcMe 中間要增長 (self *Tea) ,這種語法糖的做用簡單說就是當前這個 Me 方法被 Tea 這個類包含(實現)了,之後能夠 Tea.Me() 這麼用了。

在兩個方法裏須要實現具體的邏輯,要輸出對自身的描述和價格,那值從哪兒來,因而給 Tea 定義了兩個私有字段 nameprice,以便在構造類實例時對其賦值。

Tea 中還包含了一個 Beverage,意思是經過組合的方式讓類有了 Beverage 對象,但我的理解在本例中沒有起到實質做用,由於 Tea 已是 Beverage 的具體實現了,除非再建立出茶下面的紅茶、綠茶繼承自茶,它可用被用作標記上層結構是誰,不然在本例中只有茶一種飲品,或建立咖啡這種與茶是平級關係的構件,那這個內部的 Beverage 就沒有做用了。

package component

type Moli struct {
	*Tea
}

func NewMoli() *Moli {
	return &Moli{&Tea{name: "茉莉", price: 48}}
}

func (self *Moli) Me() string{
	return self.name
}

func (self *Moli) Cost() int {
	return self.price
}
package component

type Puer struct {
	*Tea
}

func NewPuer() *Puer {
	return &Puer{&Tea{name: "普洱", price: 38}}
}

func (self *Puer) Me() string{
	return self.name
}

func (self *Puer) Cost() int {
	return self.price
}

上面建立兩種具體的茶,茉莉和普洱。能夠看到兩種茶實際是一種結構,爲了表達裝飾模式的特性這樣寫更爲清晰。類中只包含了一個對象,就是指向茶的指針,也就是「指向某個茶的指針」。普洱類就像個殼,名字叫普洱,殼裏邊只有一種(個)對象就是茶。

NewPuer的語法能夠幫助咱們方便的實例化一個普洱,它的返回值是指針,內在的邏輯是返回一個袋子,這種袋子叫 Puer,它裏面(只)有一種(個)東西名叫普洱價格是38元的茶。茉莉邏輯與此相同。

到此,完成了抽象構件和具體構件的設計和建立,實際能夠喝茶了,沏上兩杯試一下

package main

func main() {

	moli := component.NewMoli()
	puer := component.NewPuer()

	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 1, moli.Me(), moli.Cost())
	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 2, puer.Me(),puer.Cost())

	fmt.Printf("好喝嗎,歡迎再來 ^_^ ")
}

上面代碼會輸出兩杯茶的信息

第 1 杯是 茉莉 售價 48 元
第 2 杯是 普洱 售價 38 元

下面該裝飾了,我要建立一些輔料,好比糖和冰,並但願能自由的放進想放的飲料中而不會和某種飲料硬性綁定,最終實現的邏輯是點一杯加糖的茉莉而不是點一杯茉莉本身再買一包糖倒裏邊。按照原理先定義出一個抽象裝飾,它要一樣實現抽象構件 Beverage 接口,並(最好)還能保持對構件的引用,由於要有「本體」才能裝飾,否則對誰作裝飾呢。

package decorator

import "golang-design-pattern/decorator/component"

type Condiment struct {
	*component.Tea //做用?
	beverage component.Beverage
	name string
	price int
}

func (self *Condiment) Me() string {
	return self.name
}

func (self *Condiment) Cost() int {
	return self.price
}

上面是抽象裝飾 Condiment,與 Tea 同樣實現了兩個具體的方法,並擁有兩個方法要使用到的私有字段。beverage component.Beverage 讓它可以保存一個符合抽象構件接口要求的對象,即只要是知足 Beverage 接口定義的對象我就能保存着之後用。

package decorator

import "golang-design-pattern/decorator/component"

type Sugar struct {
	*Condiment
}

func NewSugar(beverage component.Beverage) *Sugar {
	return &Sugar{ &Condiment{beverage:beverage, name:"糖", price:3 }}
}

func (self *Sugar) Me() string{
	return self.beverage.Me() + " 加點 " + self.name
}

func (self *Sugar) Cost() int {
	return self.beverage.Cost() + self.price
}
package decorator

import "golang-design-pattern/decorator/component"

type Ice struct {
	*Condiment
}

func NewIce(beverage component.Beverage) *Ice {
	return &Ice{ &Condiment{beverage: beverage, name: "冰", price: 3 }}
}

func (self *Ice) Me() string {
	return "加了" + self.name + "的" + self.beverage.Me()
}

func (self *Ice) Cost() int {
	return self.beverage.Cost() + self.price
}

上面定義兩種輔料,SugarIce。角色是具體裝飾,內部保存着對 Condiment 的引用,而且它們也要實現 Beverage 接口,是爲了履行裝飾模式的特性,即對上層調用是透明的,調用裝飾件和調用具體構件方法同樣,不然就違背或污染了裝飾模式的優點。

具體裝飾的接口方法是關鍵,以 Sugar 中的 Cost 方法爲例,它的實現是經過將 Sugar (裏)的 Condiment (裏)的 beverage 的價格疊加上 Sugar 本身的價格,做爲這一杯「加糖飲料」的價格。

好了,輔料也有了,讓咱們來作一杯加糖的茉莉和加冰的普洱吧

package main

import (
	"fmt"
	"golang-design-pattern/decorator/component"
	"golang-design-pattern/decorator/decorator"
)

func main() {

	moli := component.NewMoli()
	puer := component.NewPuer()

	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 1, moli.Me(), moli.Cost())
	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 2, puer.Me(),puer.Cost())

	fmt.Printf("下面咱們給剛纔那杯茉莉加點糖...\n")
	sugar := decorator.NewSugar(moli)
	fmt.Printf("剛剛給茉莉加了點糖,如今準備嘗一下\n")
	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 3, sugar.Me(), sugar.Cost())

	ice := decorator.NewIce(puer)
	fmt.Printf("來一杯加冰的普洱,如今準備嘗一下\n")
	fmt.Printf("第 %v 杯是 %s 售價 %v 元\n", 4, ice.Me(), ice.Cost())
	fmt.Printf("好喝嗎,歡迎再來 ^_^ ")

首先作了兩杯標準的茶,一杯48元叫作「茉莉」的Moli茶,一杯38元的叫作「普洱」的Puer茶。爲了給這杯Moli加點糖,建立了 sugar,並把剛纔那杯 moli 「傳」給了它。它拿到了 moli 後加了糖(sugar.Me方法),並把價格提升到了 48+3 元,加冰的Puer亦是如此。

感覺

爲何要把本體傳給裝飾,而不是往本體上「添加」裝飾,這個邏輯讓我想不通彆扭了好久,其實到如今也是彆扭。越彆扭越佩服創造邏輯創造模式的聰明人,由於在本體上作動做,必定會增長本體的額外工做,甚至會破壞本體原有的結構,本體會怎麼想,我就是一杯茉莉茶,我爲何要實現加糖、加醋、加冰這些方法。因此換個角度看,把具體裝飾想象成一個廚師,把本體(具體構件)給TA,TA來作操做就好理解一些了。

單點一杯Moli,再點一個Sugar,把它們加一塊兒也能達成效果,這和裝飾模式有什麼區別?我的理解裝飾模式是「官方組裝」,是對於客戶端而言的。客戶端須要一杯加了糖的茉莉茶,這是一杯通過組合加工的總體產品交付,而不是扔給客戶一杯茶一袋糖,這有本質的區別。在應用中,裝飾模式每每被用來作更有趣的功能擴展,核心優勢是經過「組合」而不是「繼承」的方式,在不改變本體的狀況下,改變結果

必定要儘可能理解邏輯自己的邏輯,而不能僅從文字字面意思理解,中文英文自己意思就差距甚遠,更況且中文本身又那麼博大精深。

參考與引用


CSDN Shulin

博客園 灌水樂園

原文出處:https://www.cnblogs.com/cinlap/p/11654927.html

相關文章
相關標籤/搜索