简介
我们经常会遇到“给现有对象/模块新增功能”的场景,比如 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 的这个功能场景,很适合使用装饰者模式来实现,代码如下:
总结实现装饰者模式的几个关键点:
- 定义需要被装饰的抽象接口,后续的装饰器都是基于该接口进行扩展。
- 为抽象接口提供一个基础实现。
- 定义装饰器,并实现被装饰的抽象接口。
- 装饰器持有被装饰的抽象接口作为成员属性。“装饰”的意思是在原有功能的基础上扩展新功能,因此必须持有原有功能的抽象接口。
- 在装饰器中,对于需要扩展功能的方法,新增扩展功能。
- 不需要扩展功能的方法,直接调用被装饰接口的原生方法即可。
- 为装饰器定义一个工厂方法,入参为被装饰接口。
- 使用时,通过装饰器的工厂方法,把所有装饰器和被装饰者串联起来。
扩展
Go 风格的实现
SocketSocketnet/httphttp.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等功能。
- ......
优缺点
优点
- 遵循开闭原则,能够在不修改老代码的情况下扩展新功能。
- 可以用多个装饰器把多个功能组合起来,理论上可以无限组合。
缺点
- 一定要注意装饰器装饰的顺序,否则容易出现不在预期内的行为。
- 当装饰器越来越多之后,系统也会变得复杂。
与其他模式的关联
装饰者模式和代理模式具有很高的相似性,但是两种所强调的点不一样。前者强调的是为本体对象添加新的功能;后者强调的是对本体对象的访问控制。
装饰者模式和适配器模式的区别是,前者只会扩展功能而不会修改接口;后者则会修改接口。
文章配图
可以在 用Keynote画出手绘风格的配图 中找到文章的绘图方法。