简介

我们经常会遇到“给现有对象/模块新增功能”的场景,比如 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 结构

场景上下文

network.Socketnetwork.Socket

代码实现

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

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

  1. 定义需要被装饰的抽象接口,后续的装饰器都是基于该接口进行扩展。
  2. 为抽象接口提供一个基础实现。
  3. 定义装饰器,并实现被装饰的抽象接口。
  4. 装饰器持有被装饰的抽象接口作为成员属性。“装饰”的意思是在原有功能的基础上扩展新功能,因此必须持有原有功能的抽象接口。
  5. 在装饰器中,对于需要扩展功能的方法,新增扩展功能。
  6. 不需要扩展功能的方法,直接调用被装饰接口的原生方法即可
  7. 为装饰器定义一个工厂方法,入参为被装饰接口。
  8. 使用时,通过装饰器的工厂方法,把所有装饰器和被装饰者串联起来。

扩展

Go 风格的实现

SocketSocket
net/http
http.HandleFunchellofunc(w http.ResponseWriter, r *http.Request)

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

http.HandlerFuncfunc(http.HandlerFunc) http.HandlerFuncDecorateWithBasicAuthWithLoggerDecorate(hello, WithLogger, WithBasicAuth)

Go 标准库中的装饰者模式

context

使用时,可以这样:

contextcontext

典型使用场景

  • I/O 流,比如为原始的 I/O 流增加缓冲、压缩等功能。
  • Http Router,比如为基础的 Http Router 能力增加日志、鉴权、Cookie等功能。
  • ......

优缺点

优点

  1. 遵循开闭原则,能够在不修改老代码的情况下扩展新功能。
  2. 可以用多个装饰器把多个功能组合起来,理论上可以无限组合。

缺点

  1. 一定要注意装饰器装饰的顺序,否则容易出现不在预期内的行为。
  2. 当装饰器越来越多之后,系统也会变得复杂。

与其他模式的关联

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

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

文章配图

可以在 用Keynote画出手绘风格的配图 中找到文章的绘图方法。