装饰器模式
装饰器模式: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
UML类图:
分析
首先我们需要理解,为什么组合优于继承?
- 继承有诸多作用,但继承层次过深、过复杂,会影响到代码的可维护性。
- 继承主要有三个作用:表示is-a关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。
再来看一下装饰器模式和代理模式的区别。
- 从UML层面上,Proxy下没有子类,Decorator下却有子类。Proxy和Decorator有一部分作用是相同的,都需要包一下原始类的各个函数,只不过Proxy在包的过程中,会附加一些和原始类无关的功能。Proxy包原始类各个函数,主要是为了让ConcreteDecorator减少重复操作,只关注要增加功能的函数即可。
- 从使用层面上说,Proxy很直接,客户端调用Proxy,Proxy调用原始类。装饰器模式则可以嵌套使用,A装饰B,B装饰C,C装饰D。
可能大家会问组合作用到底在哪?我们想一个场景,假设一个类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)
}
总结
装饰器模式理解和使用都比较简单,主要通过组合方式实现复用能力,如果组合的变量为接口或者基类,便可实现串联功能。
在使用上,首先需要确定复用的功能抽象的比较好,以免使用的时候,发现很多增强功能可以收敛其中。其次判断是否有增强的功能需要串联的情况,如果有的话,使用装饰器模式是十分合适的。
装饰器模式体现了开闭原则、里氏替换原则、依赖倒转原则。
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: