实践GoF的23种设计模式:装饰者模式

新卡尝鲜记:RTX 5070Ti适配PyTorch生态的实战避坑指南 本文分享了RTX 5070Ti适配PyTorch生态的实战经验。针对CUDA 12.8兼容性问题,详细介绍了安装PyTorch Nightly版本、手动编译xformers库以及寻找PyTorch3D第三方预编译包的完整流程与解决方案,帮助用户快速搭建可用的深度学习开发环境。 阅读详情

简介

我们经常会遇到“ 给现有对象/模块新增功能 ”的场景,比如 http router 的开发场景下,除了最基础的路由功能之外,我们常常还会加上如日志、鉴权、流控等 middleware。如果你查看框架的源码,就会发现 middleware 功能的实现用的就是 装饰者模式 (Decorator Pattern)。

GoF 给装饰者模式的定义如下:

Decorators provide a flexible alternative to subclassing for extending functionality. Attach additional responsibilities to an object dynamically.

简单来说,装饰者模式通过 组合 的方式,提供了 能够动态地给对象/模块扩展新功能 的能力。理论上,只要没有限制,它可以一直把功能叠加下去,具有很高的灵活性。

如果写过 Java,那么一定对 I/O Stream 体系不陌生,它是装饰者模式的经典用法,客户端程序可以动态地为原始的输入输出流添加功能,比如按字符串输入输出,加入缓冲等,使得整个 I/O Stream 体系具有很高的可扩展性和灵活性。

UML 结构

场景上下文

在 简单的分布式应用系统 (示例代码工程)中,我们设计了 Sidecar 边车模块,它的用处主要是为了 1)方便扩展  network.Socket  的功能,如增加日志、流控等非业务功能;2)让这些附加功能对业务程序隐藏起来,也即业务程序只须关心看到  network.Socket  接口即可。

代码实现

Sidecar 的这个功能场景,很适合使用装饰者模式来实现,代码如下:

 // demo/network/socket.go
 package network
 ​
 // 关键点1: 定义被装饰的抽象接口
 // Socket 网络通信Socket接口
 type Socket interface {
     // Listen 在endpoint指向地址上起监听
     Listen(endpoint Endpoint) error
     // Close 关闭监听
     Close(endpoint Endpoint)
     // Send 发送网络报文
     Send(packet *Packet) error
     // Receive 接收网络报文
     Receive(packet *Packet)
     // AddListener 增加网络报文监听者
     AddListener(listener SocketListener)
 }
 ​
 // 关键点2: 提供一个默认的基础实现
 type socketImpl struct {
     listener SocketListener
 }
 ​
 func DefaultSocket() *socketImpl {
     return &socketImpl{}
 }
 ​
 func (s *socketImpl) Listen(endpoint Endpoint) error {
     return Instance().Listen(endpoint, s)
 }
 ... // socketImpl的其他Socket实现方法
 ​
 ​
 // demo/sidecar/flowctrl_sidecar.go
 package sidecar
 ​
 // 关键点3: 定义装饰器,实现被装饰的接口
 // FlowCtrlSidecar HTTP接收端流控功能装饰器,自动拦截Socket接收报文,实现流控功能
 type FlowCtrlSidecar struct {
   // 关键点4: 装饰器持有被装饰的抽象接口作为成员属性
     socket network.Socket
     ctx    *flowctrl.Context
 }
 ​
 // 关键点5: 对于需要扩展功能的方法,新增扩展功能
 func (f *FlowCtrlSidecar) Receive(packet *network.Packet) {
     httpReq, ok := packet.Payload().(*http.Request)
     // 如果不是HTTP请求,则不做流控处理
     if !ok {
         f.socket.Receive(packet)
         return
    }
     // 流控后返回429 Too Many Request响应
     if !f.ctx.TryAccept() {
         httpResp := http.ResponseOfId(httpReq.ReqId()).
             AddStatusCode(http.StatusTooManyRequest).
             AddProblemDetails("enter flow ctrl state")
         f.socket.Send(network.NewPacket(packet.Dest(), packet.Src(), httpResp))
         return
    }
     f.socket.Receive(packet)
 }
 ​
 // 关键点6: 不需要扩展功能的方法,直接调用被装饰接口的原生方法即可
 func (f *FlowCtrlSidecar) Close(endpoint network.Endpoint) {
     f.socket.Close(endpoint)
 }
 ... // FlowCtrlSidecar的其他方法
 ​
 // 关键点7: 定义装饰器的工厂方法,入参为被装饰接口
 func NewFlowCtrlSidecar(socket network.Socket) *FlowCtrlSidecar {
     return &FlowCtrlSidecar{
         socket: socket,
         ctx:    flowctrl.NewContext(),
    }
 }
 ​
 // demo/sidecar/all_in_one_sidecar_factory.go
 // 关键点8: 使用时,通过装饰器的工厂方法,把所有装饰器和被装饰者串联起来
 func (a AllInOneFactory) Create() network.Socket {
     return NewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()), a.producer)
 }

总结实现装饰者模式的几个关键点:

  1. 定义需要被装饰的抽象接口 ,后续的装饰器都是基于该接口进行扩展。

  2. 为抽象接口提供一个基础实现。

  3. 定义装饰器,并实现被装饰的抽象接口。

  4. 装饰器持有被装饰的抽象接口作为成员属性 。“装饰”的意思是在原有功能的基础上扩展新功能,因此必须持有原有功能的抽象接口。

  5. 在装饰器中,对于需要扩展功能的方法,新增扩展功能。

  6. 不需要扩展功能的方法, 直接调用被装饰接口的原生方法即可 。

  7. 为装饰器定义一个工厂方法,入参为被装饰接口。

  8. 使用时,通过装饰器的工厂方法,把所有装饰器和被装饰者串联起来。

扩展

Go 风格的实现

在 Sidecar 的场景上下文中,被装饰的  Socket  是一个相对复杂的接口,装饰器通过实现  Socket  接口来进行功能扩展,是典型的面向对象风格。

如果被装饰者是一个简单的接口/方法/函数,我们可以用更具 Go 风格的实现方式,考虑前文提到的 http router 场景。如果你使用原生的  net/http  进行 http router 开发,通常会这么实现:

 func main() {
   // 注册/hello的router
     http.HandleFunc("/hello", hello)
   // 启动http服务器
     http.ListenAndServe("localhost:8080", nil)
 }
 ​
 // 具体的请求处理逻辑,类型是 http.HandlerFunc
 func hello(w http.ResponseWriter, r *http.Request) {
     w.Write([]byte("hello, world"))
 }

其中,我们通过  http.HandleFunc  来注册具体的 router,  hello  是具体的请求处理方法。现在,我们想为该 http 服务器增加日志、鉴权等通用功能,那么可以把  func(w http.ResponseWriter, r *http.Request)  作为被装饰的抽象接口,通过新增日志、鉴权等装饰器完成功能扩展。

 // demo/network/http/http_handle_func_decorator.go
 ​
 // 关键点1: 确定被装饰接口,这里为原生的http.HandlerFunc
 type HandlerFunc func(ResponseWriter, *Request)
 ​
 // 关键点2: 定义装饰器类型,是一个函数类型,入参和返回值都是 http.HandlerFunc 函数
 type HttpHandlerFuncDecorator func(http.HandlerFunc) http.HandlerFunc
 ​
 // 关键点3: 定义装饰函数,入参为被装饰的接口和装饰器可变列表
 func Decorate(h http.HandlerFunc, decorators ...HttpHandlerFuncDecorator) http.HandlerFunc {
     // 关键点4: 通过for循环遍历装饰器,完成对被装饰接口的装饰
     for _, decorator := range decorators {
         h = decorator(h)
    }
     return h
 }
 ​
 // 关键点5: 实现具体的装饰器
 func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
     return func(w http.ResponseWriter, r *http.Request) {
         cookie, err := r.Cookie("Auth")
         if err != nil || cookie.Value != "Pass" {
             w.WriteHeader(http.StatusForbidden)
             return
        }
         // 关键点6: 完成功能扩展之后,调用被装饰的方法,才能将所有装饰器和被装饰者串起来
         h(w, r)
    }
 }
 ​
 func WithLogger(h http.HandlerFunc) http.HandlerFunc {
     return func(w http.ResponseWriter, r *http.Request) {
         log.Println(r.Form)
         log.Printf("path %s", r.URL.Path)
         h(w, r)
    }
 }
 ​
 func hello(w http.ResponseWriter, r *http.Request) {
     w.Write([]byte("hello, world"))
 }
 ​
 func main() {
     // 关键点7: 通过Decorate函数完成对hello的装饰
     http.HandleFunc("/hello", Decorate(hello, WithLogger, WithBasicAuth))
     // 启动http服务器
     http.ListenAndServe("localhost:8080", nil)
 }

上述的装饰者模式的实现,用到了类似于  Functional Options  的技巧,也是巧妙利用了 Go 的函数式编程的特点,总结下来有如下几个关键点:

  1. 确定被装饰的接口,上述例子为  http.HandlerFunc 。

  2. 定义装饰器类型,是一个函数类型,入参和返回值都是被装饰接口 ,上述例子为  func(http.HandlerFunc) http.HandlerFunc 。

  3. 定义装饰函数, 入参为被装饰的接口和装饰器可变列表 ,上述例子为  Decorate  方法。

  4. 在装饰方法中, 通过for循环遍历装饰器,完成对被装饰接口的装饰 。这里是用来类似  Functional Options  的技巧, 一定要注意装饰器的顺序 !

  5. 实现具体的装饰器,上述例子为  WithBasicAuth  和  WithLogger  函数。

  6. 在装饰器中,完成功能扩展之后,记得调用被装饰者的接口,这样才能将所有装饰器和被装饰者串起来。

  7. 在使用时,通过装饰函数完成对被装饰者的装饰,上述例子为  Decorate(hello, WithLogger, WithBasicAuth) 。

Go 标准库中的装饰者模式

在 Go 标准库中,也有一个运用了装饰者模式的模块,就是  context ,其中关键的接口如下:

 package context
 ​
 // 被装饰接口
 type Context interface {
     Deadline() (deadline time.Time, ok bool)
     Done() <-chan struct{}
     Err() error
     Value(key any) any
 }
 ​
 // cancel装饰器
 type cancelCtx struct {
     Context // 被装饰接口
     mu       sync.Mutex
     done     atomic.Value
     children map[canceler]struct{}=
     err      error
 }
 // cancel装饰器的工厂方法
 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
     // ...  
     c := newCancelCtx(parent)
     propagateCancel(parent, &c)
     return &c, func() { c.cancel(true, Canceled) }
 }
 ​
 // timer装饰器
 type timerCtx struct {
     cancelCtx // 被装饰接口
     timer *time.Timer
 ​
     deadline time.Time
 }
 // timer装饰器的工厂方法
 func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
   // ...
     c := &timerCtx{
         cancelCtx: newCancelCtx(parent),
         deadline:  d,
    }
     // ...
   return c, func() { c.cancel(true, Canceled) }
 }
 // timer装饰器的工厂方法
 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
     return WithDeadline(parent, time.Now().Add(timeout))
 }
 ​
 // value装饰器
 type valueCtx struct {
     Context // 被装饰接口
     key, val any
 }
 // value装饰器的工厂方法
 func WithValue(parent Context, key, val any) Context {
     if parent == nil {
         panic("cannot create context from nil parent")
    }
   // ...
     return &valueCtx{parent, key, val}
 }

使用时,可以这样:

 // 使用时,可以这样
 func main() {
     ctx := context.Background()
     ctx = context.WithValue(ctx, "key1", "value1")
     ctx, _ = context.WithTimeout(ctx, time.Duration(1))
     ctx = context.WithValue(ctx, "key2", "value2")
 }

不管是 UML 结构,还是使用方法, context  模块都与传统的装饰者模式有一定出入,但也不妨碍  context  是装饰者模式的典型运用。还是那句话, 学习设计模式,不能只记住它的结构,而是学习其中的动机和原理 。

典型使用场景

  • I/O 流 ,比如为原始的 I/O 流增加缓冲、压缩等功能。

  • Http Router ,比如为基础的 Http Router 能力增加日志、鉴权、Cookie等功能。

  • ......

优缺点

优点

  1. 遵循 开闭原则 ,能够在不修改老代码的情况下扩展新功能。

  2. 可以用多个装饰器把多个功能组合起来,理论上可以无限组合。

缺点

  1. 一定要注意装饰器装饰的顺序 ,否则容易出现不在预期内的行为。

  2. 当装饰器越来越多之后,系统也会变得复杂。

与其他模式的关联

装饰者模式和 代理模式 具有很高的相似性,但是两种所强调的点不一样。 前者强调的是为本体对象添加新的功能;后者强调的是对本体对象的访问控制 。

装饰者模式和适配器模式的区别是,前者只会扩展功能而不会修改接口;后者则会修改接口。

Cadence Allegro 17.4学习记录开始00 Cadence Allegro 有三个版本,16.6,17.2,17.4。现在主要学习最新的版本17.4.原理图设计:OrCADPCB设计:Allegro主要用到的就下面三个图片软件:PCB Editor 17.4 是用来画PCB和元器件封装的;Padstack Editor 17.4 是用来画元器件焊盘的;Capture CIS 17.4 是用来画原理图和原理图库的。其实Cadence还有很多功能,现在计划先学习以上内容,后面熟悉了这个软件,还可以继续学习放仿真等功能。 阅读详情

相关推荐

go设计模式装饰模式

go设计模式装饰模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧。 亓斌的设计模式-装饰模式(Go语言描述) jeanphorn的Golang设计模式装饰模式 七八月份的太阳的Golang装饰设计模式(九) 我个人都不是很满意,所以才自己从头开始写关于go语言装饰模式,由于go的特殊性质,装饰模式也是相对复杂的,因为go是在c/c++基础上发展而来的,...

FlyWine的博客 886

Go实现设计模式--装饰者(器)模式

文章目录定义介绍代码示例 定义 动态的将责任附加到对象上。若要拓展功能,装饰者提供了比继承更有弹性的替代方案。 介绍 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。 何时使用:在不想增加很多子类的情况下扩展类。 如何解决:将具体功能...

hello_bravo_的博客 351

functionflow-release:功能流的发布存储库

功能流程 Function Flow 是一个以“轻松并行编程”为目标的库,特别是表达任务并行性。 当我们说“轻松并行编程”时,我们指的是很多东西,包括 1. easy used programming interfaces; 2. automatically performance tuning; 3. tool chains with static program analysis. 在易用的编程接口部分,我们精心设计了Function Flow,避免任何“反直觉”的编程语句。 所有表达并行性的事情都是直接完成的,没有初始化、任务约束、等待表达的技巧等。 此外,Function Flow 还通过利用编译时检查而不是运行时断言,为使并行编程“安全”做出了巨大努力。 这使得成功编译的函数流程序已经满足一些并行约束。 这样的功能在很大程度上有助于提高生产力。 此外,我们自定义了一些编译错误

Go讲解:装饰模式

装饰模式的主要目的是提供一种灵活的方式来为对象添加新的行为或职责,而不需要修改对象的原始类。通过引入装饰者类,可以在运行时动态地组合不同的功能,而不需要创建大量的子类。装饰模式的核心思想是将对象包装在一个或多个装饰者中,每个装饰者都可以在调用被装饰对象的方法之前或之后添加额外的行为。首先,定义一个组件接口,表示文本对象的通用行为。// TextComponent 定义了文本对象的通用行为然后,定义一个装饰者接口,持有一个组件对象的引用,并实现了组件接口。

软件架构师笔记 1243

Golang设计模式——22装饰模式

package Decorator import ( "log" "math" "time" ) func pi(n int) float64 { ch := make(chan float64) for k := 0; k < n; k++ { go func(ch chan float64, k float64) { ch <- 4 * math.Pow(-1, k) / (2*k + 1) }(ch, float64(k)) } result := 0.0

清风 743

【寒江雪】Go实现装饰模式

Decorator Pattern   装饰模式可以在需要扩展某个类的时候,动态地修改而不需要在内部添加代码,也可以防止类爆炸。    装饰模式可以提供了灵活地扩展方案. 实现   实现一个日志的自定义功能. package decorator import ( "time" "fmt" ) type LogDecorate interface { ...

寒江雪的博客 863

【Go实现】实践GoF23设计模式装饰模式

装饰模式通过组合的方式,提供了能够动态地给对象/模块扩展新功能的能力。理论上,只要没有限制,它可以一直把功能叠加下去,具有很高的灵活性。

ruanrunxue的博客 326

设计模式中并没有严格定义“15个高频”这一官方分类,但根据《设计模式:可复用面向对象软件的基础》

|------|----------| | 追求绝对安全、简洁、防攻击(尤其安全敏感系统) | ✅ 枚举单例 | | 需要延迟加载 + 高性能 + 兼容老代码风格 | ✅ 静态内部类 | | 必须用普通类(如需继承、实现接口)、且团队熟悉DCL | ✅ 正确实现的双重检查锁(带 `volatile`) | | 学习/面试考察原理深度 | 必须掌握 DCL 的 `volatile` 作用与重排序机制 |

[Blog][Domain] programb.blog.csdn.net 33

2.设计模式_02_结构型设计模式

本文摘要主要介绍了GoF 23设计模式的分类以及代理模式的实现方式。设计模式分为创建型(5种)、结构型(7种)和行为型(11种)三大类。重点阐述了代理模式的概念、应用场景及实现方式,包括静态代理和动态代理。静态代理部分详细说明了硬编码方案和继承方案的优缺点,并通过订单系统的案例展示了如何在不修改源代码的情况下通过代理模式实现性能监控功能。代理模式在需要保护目标对象、增强功能或复用代码时非常有用,是软件开发中常用的重要设计模式

u011488477的博客 1250

23设计模式GOF

设计模式GOF)分为三种类型,共23类。 一、创建型模式: 单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。 二、结构型模式: 适配器模式、桥接模式装饰模式、组合模式、外观模式、享元模式、代理模式。 三、行为型模式: 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。 目录 1、单例模式 (Singleton) 2、原型模式 (Prototype) 3...

m0_61008247的博客 6086

GoF23设计模式的分类和功能

GoF设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software(中译本名为《设计模式——可复用面向对象软件的基础》)的四位作者,他们分为是:Elich Gamma、Richard Helm、Ralph Johnson、以及John Vlissides。这四个人常被称为Gang of Four, 即四人组,简称GoF

青衫磊落长歌行的博客 6183

GoF23设计模式

设计模式有两种分类方法:根据模式的目的来分和根据模式的作用范围 1. 根据目的来分 根据模式用来完成什么工作的来划分,这种方式可分为创建型模式、结构型模式、行为模式。 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。 行为型模式:用于描述类或对象之间怎样相互协

guyuweilove的博客 4015

玄子Share 设计模式 GOF23种 + 七大设计原则

此文主要内容为,面向对象七大设计原则(OOD Principle),GOF(Gang Of Four)23设计模式,拓展的两个设计模式,简单工厂模式(Simple Factory Pattern),空对象模式(Null Object Pattern),以及工厂模式与策略模式(Factory And Strategy Pattern)之间的区别,两个设计模式较为相似,细节上需要分清,每个设计模式都分为了八步介绍,先对设计模式进行简单介绍,尽量一句话概述,方便记忆,再以现实生活中的场景举例,抛出问题解决问题

qq_62283694的博客 3436

五万字详解“GoF”的23设计模式

大家好,我是栗筝i,近期我总结梳理了 “GoF”的 23设计模式,并使用 Java 对每种设计模式都进行了伪代码与 Demo 实现,并总结了每种设计模式的应用场景,优缺点,UML图等相关内容,字/词数达到了5万,希望对大家有所帮助~~

栗筝i的博客 4132

Go语言实现装饰模式

装饰模式简介类图代码实现总结 简介 装饰模式是这样的一种模式,即在原有类的基础上进行装饰,然后添加新的功能模式,比如一家咖啡连锁店卖咖啡,可能需要按照顾客的要求进行个性化配置,需要添加多种原料并计算价格。 类图 我们构造的类图如下所示: 这里是个简单的装饰器模型,利用组合和实现的联系,以要添加的原料作为主对象完成对深色烘培咖啡的加料和价格的计算,其中摩卡和牛奶对象被称为装饰者。 代码实现 package main import "fmt" type component interface {

csdn闻声即知人的博客 386

golang设计模式(9)装饰模式

golang装饰模式

课前的博客 1813

设计模式golang-装饰模式

装饰模式 定义 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。 角色 1.装饰抽象接口 2.装饰者 3.被装饰者 角色关系:装饰者和被装饰者通过装饰抽象接口关联。 例子 一个早餐铺,卖早餐事物包子、豆浆、鸡蛋,客户可以组合购买。 //抽象装饰接口 type Food interface { GetFoodDesc()string Cost()float64 }...

感悟聚集的博客 367

【论文整理】Function Flow

Serverless execution of scientific workflows: Experiments with hyperflow, aws lambda and google cloud functions Serverless execution of scientific workflows Comparison of faas orchestration systems GPU enabled serverless computing framework

qq_34395746的博客 492

Golang设计模式(装饰模式)

模式定义 装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。 主要是给原始对象增强功能 模式结构 装饰模式包含如下角色: Component: 抽象构件 ConcreteComponent: 具体构件 Decorator

qq_43490063的博客 425

lpc1114 学习资料及例程源代码

lpc1114 学习资料及例程源代码

上一篇: 字节跳动数据平台技术:基于 ClickHouse 的复杂查询实现与优化
下一篇: 一文详解|Go 分布式链路追踪实现原理
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值