概念
“用於代替繼承的技術,無需通過繼承增加子類就能擴展對象的新功能”
“動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活”
何時用
需要擴展一個類的功能,或給一個類增加附加責任
需要動態的給一個對象增加功能,且可以動態地撤銷它
需要增加一些基本功能的排列組合而產生的大量的功能,而使得繼承變得非常困難的時候
實現構件
抽象構件(Component)
表示“被”裝飾的本體的抽象定義,這個定義通常是一個接口(Interface),定義了若干方法(能力),這些方法可以用來在被具體裝飾角色(ConcreteDecorator)實現時改變原有構件本體的方法(能力),如原來本體傷害輸出是 10,裝飾角色把它再增加 10 而不會影響本體的原有邏輯(代碼)。
具體構件(ConcreteComponent)
表示實現了抽象構件(Component)的對象,即將要接受附加能力的對象,本文中比喻的“本體”。
抽象裝飾(Decorator)
持有一個抽象構件的實例(即具體構件),並定義(實現)與抽象構件接口一致的接口。抽象裝飾的(部分)作用應該是應用了依賴倒置原則,在結構上使具體裝飾(ConcreteDecorator)不要直接依賴於抽象構件,因為二者作用性質也不同,直接依賴灰常奇怪,就好像都是50歲的男子也不能把隔壁老王當成爸爸一樣。
具體裝飾(ConcreteDecorator)
實現了抽象裝飾的定義,負責給構件對象添加附加能力的對象。具體裝飾通過實現抽象裝飾定義的接口,擁有了和具體構件一樣的“能力”(方法/函數/屬性),再通過抽象裝飾定義中所持有的抽象構件的實例而獲得對該實例“相同”能力的結果,並在結果上進行一些裝飾。
實現步驟
- 定義抽象構件,提供抽象接口
- 定義具體構件並實現抽象構件,構造后的具體構件即理解為“本體”,被裝飾的對象
- 定義抽象裝飾,它要做兩件事,實現抽象構件和保存一個抽象構件對象
- 定義具體裝飾,具體裝飾要實現抽象裝飾,並在實現的接口方法中對構件進行具體裝飾操作
- 之后,要增加“本體”就創建具體構件,要增加裝飾物,就創建具體裝飾
- 使用時,把本體“傳遞進”裝飾對象,在裝飾對象(同樣繼承自抽象構件)的方法里去使用本體的方法和結果,加工它,並輸出進行了“調整”的結果
原理與代碼
用 Golang 描述代碼結構(代碼模仿自 github,但不好意思忘記來自哪位作者了)
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
類要通過實現 Me
和 Cost
兩個具體方法來實現對接口的實現(這話說的)。與普通方法不同的是在 func
和 Me
中間要增加 (self *Tea)
,這種語法糖的作用簡單說就是當前這個 Me
方法被 Tea
這個類包含(實現)了,以后可以 Tea.Me()
這么用了。
在兩個方法里需要實現具體的邏輯,要輸出對自身的描述和價格,那值從哪兒來,於是給 Tea
定義了兩個私有字段 name
和 price
,以便在構造類實例時對其賦值。
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
}
上面定義兩種輔料,Sugar
和 Ice
。角色是具體裝飾,內部保存着對 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,把它們加一起也能達成效果,這和裝飾模式有什么區別?個人理解裝飾模式是“官方組裝”,是對於客戶端而言的。客戶端需要一杯加了糖的茉莉茶,這是一杯經過組合加工的整體產品交付,而不是扔給客戶一杯茶一袋糖,這有本質的區別。在應用中,裝飾模式往往被用來做更有趣的功能擴展,核心優點是通過“組合”而不是“繼承”的方式,在不改變本體的情況下,改變結果。
一定要盡量理解邏輯本身的邏輯,而不能僅從文字字面意思理解,中文英文本身意思就差距甚遠,更何況中文自己又那么博大精深。