Go设计模式(13)-装饰器模式

简介: 装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。 装饰器类和原始类继承同一个父类,这么看感觉和代理模式一样。但其实两者作用不同,装饰器模式主要用于增加功能,代理模式主要用于附加跟原始类无关的功能。

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。 装饰器类和原始类继承同一个父类,这么看感觉和代理模式一样。但其实两者作用不同,装饰器模式主要用于增加功能,代理模式主要用于附加跟原始类无关的功能。

本文UML类图链接为:https://www.processon.com/view/link/609b39d6f346fb5a37705da6

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/13decorator.go

1.定义

1.1装饰器模式

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

UML类图:

图片

1.2分析

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

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

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

  • 从UML层面上,Proxy下没有子类,Decorator下却有子类。Proxy和Decorator有一部分作用是相同的,都需要包一下原始类的各个函数,只不过Proxy在包的过程中,会附加一些和原始类无关的功能。Proxy包原始类各个函数,主要是为了让ConcreteDecorator减少重复操作,只关注要增加功能的函数即可。
  • 从使用层面上说,Proxy很直接,客户端调用Proxy,Proxy调用原始类。装饰器模式则可以嵌套使用,A装饰B,B装饰C,C装饰D。

可能大家会问组合作用到底在哪?我们想一个场景,假设一个类Base有个功能封装的很好,但是有些地方想用的话,需要再加强一下,这个类是D1,如果使用继承的话,就需要进行重载、调用基类。如果别的地方想用D1,也还需要加强部分功能,这个类是D2,使用继承,则又需要重载、调用基类。这使得继承层次过深、过复杂了,然后继承是有必要的吗?仔细想一下不用继承、直接用组合,不但能实现目标,工作量减小,而且能去掉继承的束缚。

2.使用场景

装饰器模式一般用在基类功能封装不错,但使用的时候需要对功能进行一些加强,而这些加强版的功能也会被其它加强版需要,这种就比较适合。

尼古拉斯凯奇主演的《战争之王》不知道大家看过没有。记得里面有个场景,凯奇买了一架武装直升机,这时FBI带人抓捕,凯奇将直升机和导弹分开就合法了。直升机就是那个封装特别好的类,能够长距离飞行。想用武装直升机,就在上面加导弹。想用救援直升机就在上面加医生。想用武装救援直升机,就在上面即加导弹又加医生。

我们就按照这个例子写代码吧。

3.代码实现

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了。

总结

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

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

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

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

相关文章
|
2天前
|
设计模式 Java
Java一分钟之-设计模式:装饰器模式与代理模式
【5月更文挑战第17天】本文探讨了装饰器模式和代理模式,两者都是在不改变原有对象基础上添加新功能。装饰器模式用于动态扩展对象功能,但过度使用可能导致类数量过多;代理模式用于控制对象访问,可能引入额外性能开销。文中通过 Java 代码示例展示了两种模式的实现。理解并恰当运用这些模式能提升代码的可扩展性和可维护性。
8 1
|
4天前
|
设计模式 Go 网络安全
[设计模式 Go实现] 结构型~代理模式
[设计模式 Go实现] 结构型~代理模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~享元模式
[设计模式 Go实现] 结构型~享元模式
|
4天前
|
设计模式 Go API
[设计模式 Go实现] 结构型~外观模式
[设计模式 Go实现] 结构型~外观模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~组合模式
[设计模式 Go实现] 结构型~组合模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~装饰模式
[设计模式 Go实现] 结构型~装饰模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~适配器模式
[设计模式 Go实现] 结构型~适配器模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 行为型~解释器模式
[设计模式 Go实现] 行为型~解释器模式
|
2天前
|
Ubuntu Unix Linux
【GO基础】1. Go语言环境搭建
【GO基础】1. Go语言环境搭建