![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L749SF4Q-1660286893968)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812113800453.png)]](https://1000bd.com/contentImg/2022/08/16/153628835.png)
装饰器模式: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
UML类图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCpQdKZm-1660286893969)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812121958709.png)]](https://1000bd.com/contentImg/2022/08/16/153628947.png)
首先我们需要理解,为什么组合优于继承?
再来看一下装饰器模式和代理模式的区别。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnHQy0Wj-1660286893970)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812123028389.png)]](https://1000bd.com/contentImg/2022/08/16/153629038.png)
可能大家会问组合作用到底在哪?我们想一个场景,假设一个类Base有个功能封装的很好,但是有些地方想用的话,需要再加强一下,这个类是D1,如果使用继承的话,就需要进行重载、调用基类。如果别的地方想用D1,也还需要加强部分功能,这个类是D2,使用继承,则又需要重载、调用基类。这使得继承层次过深、过复杂了,然后继承是有必要的吗?仔细想一下不用继承、直接用组合,不但能实现目标,工作量减小,而且能去掉继承的束缚。
装饰器模式一般用在基类功能封装不错,但使用的时候需要对功能进行一些加强,而这些加强版的功能也会被其它加强版需要,这种就比较适合。
尼古拉斯凯奇主演的《战争之王》不知道大家看过没有。记得里面有个场景,凯奇买了一架武装直升机,这时FBI带人抓捕,凯奇将直升机和导弹分开就合法了。直升机就是那个封装特别好的类,能够长距离飞行。想用武装直升机,就在上面加导弹。想用救援直升机就在上面加医生。想用武装救援直升机,就在上面即加导弹又加医生。
我们就按照这个例子写代码吧。
package main
import "fmt"
/**
* @Description: 飞行器接口,有fly函数
*/
type Aircraft interface {
fly()
landing()
}
/**
* @Description: 直升机类,拥有正常飞行、降落功能
*/
type Helicopter struct {
}
func (h *Helicopter) fly() {
fmt.Println("我是普通直升机")
}
func (h *Helicopter) landing() {
fmt.Println("我有降落功能")
}
/**
* @Description: 武装直升机
*/
type WeaponAircraft struct {
Aircraft
}
/**
* @Description: 给直升机增加武装功能
* @receiver a
*/
func (a *WeaponAircraft) fly() {
a.Aircraft.fly()
fmt.Println("增加武装功能")
}
/**
* @Description: 救援直升机
*/
type RescueAircraft struct {
Aircraft
}
/**
* @Description: 给直升机增加救援功能
* @receiver r
*/
func (r *RescueAircraft) fly() {
r.Aircraft.fly()
fmt.Println("增加救援功能")
}
func main() {
//普通直升机
fmt.Println("------------普通直升机")
helicopter := &Helicopter{}
helicopter.fly()
helicopter.landing()
//武装直升机
fmt.Println("------------武装直升机")
weaponAircraft := &WeaponAircraft{
Aircraft: helicopter,
}
weaponAircraft.fly()
//救援直升机
fmt.Println("------------救援直升机")
rescueAircraft := &RescueAircraft{
Aircraft: helicopter,
}
rescueAircraft.fly()
//武装救援直升机
fmt.Println("------------武装救援直升机")
weaponRescueAircraft := &RescueAircraft{
Aircraft: weaponAircraft,
}
weaponRescueAircraft.fly()
}
➜ myproject go run main.go
————普通直升机
我是普通直升机
我有降落功能
————武装直升机
我是普通直升机
增加武装功能
————救援直升机
我是普通直升机
增加救援功能
————武装救援直升机
我是普通直升机
增加武装功能
增加救援功能
代码实现中没有Decorator类,主要是因为Go组合的特性。之所以有Decorator,是因为Decorator中有component成员变量,Decorator中函数实现是调用component的函数,所以对于component中的每一个函数,Decorator都需要封装一下,否则无法使用。但是Go组合方式会自动完成这项任务,无需封装,自然也就不需要Decorator了。
下面是一个简单的画画的例子,默认的 Square 只有基础的画画功能, ColorSquare 为他加上了颜色
package decorator
// IDraw IDraw
type IDraw interface {
Draw() string
}
// Square 正方形
type Square struct{}
// Draw Draw
func (s Square) Draw() string {
return "this is a square"
}
// ColorSquare 有颜色的正方形
type ColorSquare struct {
square IDraw
color string
}
// NewColorSquare NewColorSquare
func NewColorSquare(square IDraw, color string) ColorSquare {
return ColorSquare{color: color, square: square}
}
// Draw Draw
func (c ColorSquare) Draw() string {
return c.square.Draw() + ", color is " + c.color
}
func TestColorSquare_Draw(t *testing.T) {
sq := Square{}
csq := NewColorSquare(sq, "red")
got := csq.Draw()
assert.Equal(t, "this is a square, color is red", got)
}
装饰器模式理解和使用都比较简单,主要通过组合方式实现复用能力,如果组合的变量为接口或者基类,便可实现串联功能。
在使用上,首先需要确定复用的功能抽象的比较好,以免使用的时候,发现很多增强功能可以收敛其中。其次判断是否有增强的功能需要串联的情况,如果有的话,使用装饰器模式是十分合适的。
装饰器模式体现了开闭原则、里氏替换原则、依赖倒转原则。