go中无传统装饰器,但可通过高阶函数和接口组合模拟:常用闭包包装函数以增强行为(如日志、超时),保持原函数签名;结构体嵌入适用于有状态场景,需显式调用被装饰对象方法。

Go 里没有传统装饰器,但可以用函数组合模拟
Go 语言本身不支持 Python 那种 @decorator 语法,也没有类继承意义上的“装饰器模式”。所谓“Go 装饰器”,本质是利用高阶函数 + 接口组合,在运行时动态增强行为。关键不是套概念,而是解决具体问题:比如给一个 http.HandlerFunc 加日志、超时、鉴权,又不想侵入原逻辑。
用闭包包装函数是最常用且安全的做法
直接返回新函数,把原始函数作为参数传入闭包,这是最符合 Go 习惯的“装饰”方式。它不修改原值,无副作用,也容易测试。
- 别试图用结构体字段存“被装饰对象”再实现同名方法——这容易绕晕,且破坏单一职责
- 如果原函数签名是
func(string) error,装饰后必须保持相同签名,否则类型不匹配 - 闭包捕获外部变量要小心生命周期:比如装饰器里引用了局部切片,而返回的函数在 goroutine 中异步调用,可能引发 data race
示例:给任意 http.HandlerFunc 加请求耗时打印
func withDuration(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r)
log.Printf("path=%s duration=%v", r.URL.Path, time.Since(start))
}
}
接口+嵌入才是面向对象风格的“装饰器”写法
当需要多次叠加、或装饰逻辑本身有状态(如计数器、缓存),用结构体嵌入目标接口更清晰。但要注意:嵌入不是继承,方法调用链不会自动代理,必须显式调用 o.next.DoSomething()。
立即学习“go语言免费学习笔记(深入)”;
- 忘记在装饰器方法里调用
o.next.Xxx()是最高频错误,结果就是“装饰了但啥也没发生” - 如果被装饰对象实现了多个接口(如
io.Reader和io.Closer),装饰器结构体得显式实现全部,不能只嵌入一个 - 嵌入指针(
*nextHandler)比嵌入值更常见,避免复制大对象;但要注意 nil 指针 panic 风险
别在装饰器里做阻塞操作,尤其 HTTP 场景
HTTP 装饰器常被误当成中间件随意加锁、查 DB、调远程服务——这会拖垮整个 handler 的并发能力。装饰器应尽量轻量,重逻辑下沉到业务层。
-
withAuth不该自己解析 JWT 并查数据库,而应调用已封装好的auth.Verify(r.Context()) - 日志装饰器里别用
log.Printf,改用结构化 logger(如zerolog)并确保写入非阻塞 - 加
context.WithTimeout是合理装饰,但 timeout 时间设成 5s 而不是默认 30s,这种策略必须可配置,不能硬编码
真正难的从来不是怎么写个闭包,而是判断哪一层该装饰、哪一层该由调用方自己控制上下文和错误处理。边界模糊时,宁可多传一个 context.Context 参数,也不在装饰器里偷偷 cancel。










