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

2023-05-16

装饰器模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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()
}
➜ 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)
}

总结

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

golang设计模式——装饰器模式 的相关文章

  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • 【设计模式】工厂模式(Factory Pattern)

    1 概述 工厂模式 Factory Pattern 是最常用的设计模式之一 它属于创建类型的设计模式 它提供了一种创建对象的最佳方式 在工厂模式中 我们在创建对象时不会对客户端暴露创建逻辑 并且是通过一个共同的接口来指向新创建的对象 工厂模
  • 【golang】error parsing regexp: invalid or unsupported Perl syntax (正则表达式校验密码)

    要在 Go 中编写密码校验规则 确保密码不少于8位且包含数字和字母 你可以使用正则表达式和 Go 的 regexp 包来实现 以下是一个示例代码 错误示范 package main import fmt regexp func valida
  • JavaScript设计模式-02-单例模式

    Javascript 设计模式 02 单例模式 简介 单例就是保证一个类只有一个实例 实现的方法一般是先判断实例是否存在 如果存在直接返回 如果不存在就创建了再返回 确保了一个类只有一个实例对象 在JavaScript里 单例作为一个命名空
  • 二十四种设计模式之策略模式

    一 什么是策略模式 简单来说 策略模式是将每一个算法封装到拥有共同接口的不同类中 使得算法可以在不影响客户端的情况下发生变化 也可以理解为可供程序运行时选择的 不同的类 不同的解决方案 策略模式的特点 高内聚低耦合 可扩展 遵循ocp原则
  • Go 语言输出文本函数详解

    Go语言拥有三个用于输出文本的函数 Print Println Printf Print 函数以其默认格式打印其参数 示例 打印 i 和 j 的值 package main import fmt func main var i j stri
  • 【go语言开发】loglus日志框架的使用

    本文将简单介绍loglus框架的基本使用 并给出demo 文章目录 前言 Loglus常见用法 自定义日志级别 使用字段钩子 输出到多个位置 使用钩子实现自定义日志处理 demo
  • go-zero开发入门-API网关开发示例

    开发一个 API 网关 代理 https blog csdn net Aquester article details 134856271 中的 RPC 服务 网关完整源代码 file main go package main import
  • “go mod tidy”之错误“not a valid zip file”

    执行 go mod tidy 时 遇到如下错误 rpc imports github com zeromicro go zero zrpc imports github com zeromicro go zero zrpc resolver
  • Go 语言中切片的使用和理解

    切片与数组类似 但更强大和灵活 与数组一样 切片也用于在单个变量中存储相同类型的多个值 然而 与数组不同的是 切片的长度可以根据需要增长和缩小 在 Go 中 有几种创建切片的方法 使用 datatype values 格式 从数组创建切片
  • 设计模式详解---策略模式

    1 策略模式简介 策略模式 Strategy Pattern 是一种行为型设计模式 用于在运行时根据不同的情境选择不同的算法或策略 该模式将算法封装成独立的类 使得它们可以相互替换 而且可以独立于客户端使用它们的方式 1 1 主要角色 上下
  • 谁能想到Java多线程设计模式竟然能被图解,大佬就是大佬,太牛了

    设计模式 Design pattern 代表了最佳的实践 通常被有经验的面向对象的软件开发人员所采用 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的
  • 设计模式(三)-结构型模式(4)-组合模式

    一 为何需要组合模式 Composite 在代码设计中 有种情况是对象之间存在层次关系 即对象之间会存在父结点和子结点的关系 比如在文件管理系统中 所有文件和文件夹形成树状结构 文件夹目录里存在子文件夹和文件 文件夹属于枝结点 文件属于叶结
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • C++设计模式 #3策略模式(Strategy Method)

    动机 在软件构建过程中 某些对象使用的的算法可能多种多样 经常改变 如果将这些算法都写在类中 会使得类变得异常复杂 而且有时候支持不频繁使用的算法也是性能负担 如何在运行时根据需求透明地更改对象的算法 将算法和对象本身解耦 从而避免上述问题
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • [每周一更]-(第55期):Go的interface

    参考地址 https juejin cn post 6978322067775029261 https gobyexample com interfaces https go dev tour methods 9 介绍下Go的interfa
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱
  • 【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架

    文章目录 一 控制反转 IOC 二 依赖注入 DI 三 依赖注入框架 DI Framework 四 依赖反转原则 DIP 一 控制反转 IOC 通过一个例子来看一下 什么是控制反转 public class UserServiceTest
  • C++设计模式 --1.工厂模式和单例模式

    文章目录 1 工厂模式 简单工厂模式 工厂方法模式 抽象工厂模式 2 单例模式 懒汉式 饿汉式 1 工厂模式 简单工厂模式

随机推荐

  • STM32 芯片锁死无法烧录问题解决

    芯片锁死原因 xff1a 1 烧进去的工程对应器件与目标器件不一致 xff1b 2 烧进去的工程HSE VALUE与目标板上晶振频率不一致 xff1b 3 将烧录引脚烧录 本人在使用F411时犯下了 比较愚蠢的错误 xff0c 因为PB3引
  • 【TPMS】 - 发射端2

    TPMS项目 发射端SP370 目录章节介绍 一 SP370数据手册浏览二 源码学习三 SP370的RF的部分详解四 RF数据包的发送和数据包格式解析1 目录 章节介绍 1 SP370数据手册浏览 浏览SP370的数据手册 xff0c 看一
  • 【TPMS】 -接收端1

    TPMS项目 接受端TDA5235 目录章节介绍 一 TPMS接收板概况介绍二 TDA5235的专业知识1三 寄存器配置工具 目录 章节介绍 1 TPMS接收板概况介绍 本节开始接收板部分的课程 xff0c 先对接收板的整体情况 xff0c
  • ST_link突然不能使用了

    一 发现问题 今天 xff0c 我使用st link烧写程序时突然不能用了 xff0c 我昨天都还可以正常使用 我用手摸仿真器时很热 我意识到可能坏了 二 解决过程 xff08 1 xff09 用keil5查看debug设置时就能找到ST
  • OpenCV 读取视频并保存为另一个视频

    测试代码如下 xff1a 功能 xff1a 读取视频 xff0c 缩小处理后再存为另一个视频 方法1 include lt opencv2 opencv hpp gt include lt opencv2 highgui highgui c
  • XML和JSON

    XML 简介 xml是什么 XML是一种可扩展标记语言 Extensible Markup Language xff0c 它是一种用于在计算机网络上进行数据传输的标准格式 XML使用标记来标识数据 xff0c 并且可以自定义标记 xff0c
  • STM32单片机蜂鸣器实验

    蜂鸣器可以分为两种 xff1a 有源蜂鸣器与无源蜂鸣器 xff0c 这里的 源 指的是有没有自带震荡电路 xff0c 有源的蜂鸣器自带有震荡电路 xff0c 通电的瞬间就会发出声音 xff1b 而无源的蜂鸣器 xff0c 需要提供一个2 5
  • JVM虚拟机

    JVM 1 JVM 概述 x1f6b4 x1f6b4 x1f6b2 x1f6b4 虚拟机 xff08 Virtual Machine xff09 是一台虚拟的计算机 VMware属于系统虚拟机 xff0c 是对物理计算机的仿真 Java虚拟
  • 树莓派桌面WIFI图标消失,树莓派黑屏can‘t currently show the desktop

    方法一 xff1a 重装镜像 方法二 xff1a 找个树莓派显示器终端输入这行代码 sudo apt install wpasupplicant wpagui libengine pkcs11 openssl 转载B站视频 xff1a 完美
  • cuda10.1+cudnn10.1+tensorflow2.2.0+pytorch1.7.1下载安装及配置

    一 cuda及cudnn下载 1 查看自己电脑是否支持GPU 方法 xff1a 鼠标移动到此电脑 xff0c 点击鼠标右键 xff0c 依次选择属性 设备管理器 显示适配器有以下图标 xff08 NVIDIA xff09 即可安装GPU x
  • C语言:strtok()函数简单用法

    strtok函数 切割字符串 第一个参数指定一个字符串 xff0c 它包含了0个或者多个由第二个参数 xff08 字符串 xff09 中的一个或多个分隔符分割的标记 strtok函数找到第一个参数中的下一个标记 xff0c 并将其用 39
  • ESP32之FreeRTOS--任务的创建和运行

    文章目录 前言一 创建任务和删除函数1 xTaskCreate 2 xTaskCreateStatic 3 xTaskCreateRestricted 4 vTaskDelete 二 任务函数和任务控制块TCB1 任务函数模板2 TCB 三
  • 如何将本地项目上传到gitee

    如何将本地项目上传到gitee 第一步 xff1a 首先你要有一个gitee仓库 新建仓库 填写仓库信息 xff1a 如图 第二步 xff1a 将创建好的仓库 xff0c pull xff08 拉取 xff09 到本地 通过git 命令 把
  • go语言操作es

    目录 go语言操作es解决golang使用elastic连接elasticsearch时自动转换连接地址初始化数据创建结构体方式字符串方式 xff1a 查找修改删除查找 集群搭建配置文件修改 go语言操作es go get github c
  • Context介绍

    目录 Context设计原理默认上下文取消信号传值方法小结 Context 上下文 context Context Go 语言中用来设置截止日期 同步信号 xff0c 传递请求相关值的结构体 上下文与 Goroutine 有比较密切的关系
  • 将视频转成ROS的bag包

    执行转化命令 python2 mp4 2 bag py lane video3 mp4 out camera bag 循环播放图片 xff0c 并重命名成自己需要的话题名 rosbag play l out camera bag camer
  • beego介绍(一)

    目录 beego 的 MVC 架构介绍参数配置默认配置解析不同级别的配置多个配置文件支持环境变量配置系统默认参数基础配置App 配置Web配置监听配置Session配置Log配置 路由设置基础路由基本 GET 路由基本 POST 路由注册一
  • TCP如何保证可靠性?

    TCP如何保证可靠性 xff1f TCP协议保证数据传输可靠性的方式主要有 xff1a 校验和 序列号 确认应答 超时重传 连接管理 流量控制 拥塞控制 1 校验和 计算方式 xff1a 在数据传输的过程中 xff0c 将发送的数据段都当做
  • 仿照java的jdk动态代理实现go语言动态代理

    仿照java的jdk动态代理实现go语言动态代理 通过学习java的jdk动态代理和Cglib动态代理 xff0c 仿照jdk动态代理用go实现了一个简单的动态代理 结构型模式 代理模式 代理模式中分为静态代理和动态代理 静态代理需要在编译
  • golang设计模式——装饰器模式

    装饰器模式 装饰器模式 xff1a 动态地给一个对象添加一些额外的职责 xff0c 就增加功能来说 xff0c 装饰模式比生成子类更为灵活 UML类图 xff1a 分析 首先我们需要理解 xff0c 为什么组合优于继承 xff1f 继承有诸