七、decorate 装饰模式
如果希望增强行为,可以使用 decorate 模式。且支持嵌套多层(套娃)。
和 proxy 模式的区别:proxy 只套了一层,而且行为相同。而 decorate 可以套多层,且行为会增强。
以具体例子,会更容易理解。
7.1 饮料:类型+配料
参考:https://www.bilibili.com/video/BV1Vp4y187dK/?spm_id_from=333.337.search-card.all.click&vd_source=5dfe92471f5072eaffbf480e25acd82d
有多种饮料类型:如奶茶、绿茶、柠檬茶。
如果希望在此基础上,添加配料:如布丁、珍珠、糖。
则可以定义【配料装饰者 interface】,并使 布丁、珍珠分别实现该 interface。
那么就可以套娃了,比如:
奶茶 = New奶茶()
加1份布丁的奶茶 = New布丁(奶茶)
加2份布丁的奶茶 = New布丁(New布丁(奶茶))
加2份布丁, 且加3份珍珠的奶茶 = New珍珠(New珍珠(New珍珠(New布丁(New布丁(奶茶)))))
上文的配料就是装饰:因为各种配料都实现了同样的 interface,所以可以层层嵌套。每层都“增强”了行为。
目录层级如下:
07decorator/071drink_ingredient
├── drink.go
├── drink_with_ingredient.go
└── drink_with_ingredient_test.go
7.1.1 drink_with_ingredient_test.go
package _71drink_ingredient
import (
"github.com/stretchr/testify/require"
"testing"
)
/*
=== RUN TestDrinkWithIngredient
一杯奶茶 20 元
一杯奶茶 20 元
加 3 元的糖后, 共 23 元.
一杯奶茶 20 元
加 3 元的糖后, 共 23 元. 加 3 元的糖后, 共 26 元.
一杯奶茶 20 元
加 3 元的糖后, 共 23 元. 加 3 元的糖后, 共 26 元. 加 1 元的冰后, 共 27 元.
--- PASS: TestDrinkWithIngredient (0.00s)
PASS
*/
func TestDrinkWithIngredient(t *testing.T) {
m := &milkTea{}
require.EqualValues(t, 20, m.cost())
t.Log()
ms := &drinkWithSugar{m}
require.EqualValues(t, 20+3, ms.cost())
t.Log()
mss := &drinkWithSugar{ms}
require.EqualValues(t, 20+3+3, mss.cost())
t.Log()
mssi := &drinkWithIce{mss}
require.EqualValues(t, 20+3+3+1, mssi.cost())
}
/*
=== RUN TestDrinkWithIngredientByInterface
一杯奶茶 20 元
加 3 元的糖后, 共 23 元. 加 3 元的糖后, 共 26 元. 加 1 元的冰后, 共 27 元.
--- PASS: TestDrinkWithIngredientByInterface (0.00s)
PASS
*/
func TestDrinkWithIngredientByInterface(t *testing.T) {
var d drinkWithIngredient = &milkTea{}
for i := 0; i < 2; i++ {
d = &drinkWithSugar{d}
}
d = &drinkWithIce{d}
require.EqualValues(t, 20+3+3+1, d.cost())
}
7.1.2 drink_with_ingredient.go
package _71drink_ingredient
import "fmt"
// 饮料装饰器: 加配料的饮料
type drinkWithIngredient interface {
// 饮料的价格
cost() int
}
// 加糖饮料
type drinkWithSugar struct {
d drinkWithIngredient
}
func (d *drinkWithSugar) cost() int {
c := 3
total := d.d.cost() + c
fmt.Printf("加 %v 元的糖后, 共 %v 元. ", c, total)
return total
}
// 加冰饮料
type drinkWithIce struct {
d drinkWithIngredient
}
func (d *drinkWithIce) cost() int {
c := 1
total := d.d.cost() + c
fmt.Printf("加 %v 元的冰后, 共 %v 元. ", c, total)
return total
}
7.1.3 drink.go
package _71drink_ingredient
import "fmt"
// 饮料
type drink interface {
// 饮料的价格
cost() int
}
// 奶茶
type milkTea struct{}
func (t *milkTea) cost() int {
c := 20
fmt.Printf("一杯奶茶 %v 元\n", c)
return c
}
// 绿茶
type greenTea struct{}
func (t *greenTea) cost() int {
c := 10
fmt.Printf("一杯绿茶 %v 元\n", c)
return c
}
// 柠檬茶
type lemonTea struct{}
func (t *lemonTea) cost() int {
c := 5
fmt.Printf("一杯柠檬茶 %v 元\n", c)
return c
}
7.2 notifier
07decorator/072notifier
├── notifier.go
├── notifier_decorator.go
└── notifier_decorator_test.go
7.2.1 notifier_decorator_test
/*
=== RUN TestNotifierDecorator
qq发送消息
wechat发送消息
phone发送消息
--- PASS: TestNotifierDecorator (0.00s)
PASS
*/
func TestNotifierDecorator(t *testing.T) {
var n notifierDecorator
n = &qqNotifierDecorator{}
n = &wechatNotifierDecorator{wrappee: n}
n = &phoneNotifierDecorator{wrappee: n}
n.notify()
}
7.2.2 notifier_decorator
package _72notifier
import "fmt"
type notifierDecorator interface {
notifier
}
type qqNotifierDecorator struct {
wrappee notifierDecorator
}
func (n *qqNotifierDecorator) notify() {
if n.wrappee != nil {
n.wrappee.notify()
}
fmt.Println("qq发送消息")
}
type wechatNotifierDecorator struct {
wrappee notifierDecorator
}
func (n *wechatNotifierDecorator) notify() {
if n.wrappee != nil {
n.wrappee.notify()
}
fmt.Println("wechat发送消息")
}
type phoneNotifierDecorator struct {
wrappee notifierDecorator
}
func (n *phoneNotifierDecorator) notify() {
if n.wrappee != nil {
n.wrappee.notify()
}
fmt.Println("phone发送消息")
}
7.2.3 notifier
package _72notifier
type notifier interface {
notify()
}