• golang设计模式——装饰器模式


    装饰器模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L749SF4Q-1660286893968)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812113800453.png)]

    装饰器模式: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

    UML类图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCpQdKZm-1660286893969)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812121958709.png)]

    分析

    首先我们需要理解,为什么组合优于继承?

    • 继承有诸多作用,但继承层次过深、过复杂,会影响到代码的可维护性。
    • 继承主要有三个作用:表示is-a关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。

    再来看一下装饰器模式和代理模式的区别。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnHQy0Wj-1660286893970)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812123028389.png)]

    • 从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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    ➜ myproject go run main.go
    
    ————普通直升机
    
    我是普通直升机
    
    我有降落功能
    
    ————武装直升机
    
    我是普通直升机
    
    增加武装功能
    
    ————救援直升机
    
    我是普通直升机
    
    增加救援功能
    
    ————武装救援直升机
    
    我是普通直升机
    
    增加武装功能
    
    增加救援功能
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    代码实现中没有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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    单元测试

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    装饰器模式理解和使用都比较简单,主要通过组合方式实现复用能力,如果组合的变量为接口或者基类,便可实现串联功能。

    在使用上,首先需要确定复用的功能抽象的比较好,以免使用的时候,发现很多增强功能可以收敛其中。其次判断是否有增强的功能需要串联的情况,如果有的话,使用装饰器模式是十分合适的。

    装饰器模式体现了开闭原则、里氏替换原则、依赖倒转原则。

  • 相关阅读:
    Gradle学习第一篇——自定义Gradle插件
    【NLP】情绪分析与酒店评论
    StorageReview公布浪潮固态硬盘与浪潮服务器性能测试报告
    如何在校园跑腿系统小程序中构建稳健的订单处理与分配系统?
    jsp 新能源汽车论坛网Myeclipse开发mysql数据库web结构java编程计算机网页项目
    Python 网络爬虫
    基于ssm+jsp(非maven)的农产品购物系统
    linux 设备树of函数学习笔记
    深度剖析js闭包
    iOS开发之编译OpenSSL静态库
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126304177