Go 语言虽无原生装饰器语法,但可通过高阶函数、接口与组合实现:统一函数签名(如含 context.Context)、链式 Decorator 接口、HTTP 中间件等,关键在契约一致与上下文透传。

Go 语言没有原生的装饰器语法(比如 Python 的 @decorator),但完全可以通过高阶函数、接口和组合来实现装饰器模式的核心意图:在不修改原函数逻辑的前提下,动态地添加行为(如日志、重试、超时、鉴权等)。
用函数类型和闭包封装装饰逻辑
Go 中最自然的装饰器实现方式是把处理逻辑抽象为「接收函数、返回函数」的高阶函数。被装饰的目标函数需统一签名(例如 func() error 或 func(context.Context) error),装饰器则在其前后插入额外操作。
常见错误是忽略函数签名一致性——比如原函数带 context.Context,而装饰器硬编码为无参,导致无法传递取消信号或超时控制。
- 推荐统一使用
func(context.Context, ...interface{}) error或更明确的业务参数结构体 +context.Context - 装饰器内部必须调用
original(ctx, args...),不能漏掉ctx传递 - 若需修改返回值(如将 panic 转为 error),需用
defer/recover包裹调用,但要注意这仅对当前 goroutine 有效
用接口+结构体实现可链式调用的装饰器
当需要多个装饰器叠加(如日志 → 重试 → 熔断),纯函数嵌套易读性差(WithCircuitBreaker(WithRetry(WithLog(f))))。此时可定义一个接口,让装饰器表现为「可执行对象」:
立即学习“go语言免费学习笔记(深入)”;
type Handler interface {
Handle(ctx context.Context, req interface{}) error
}
type Decorator func(Handler) Handler
这样就能链式注册:Chain(mw1, mw2, mw3)(finalHandler)。注意 Chain 必须保证装饰器按顺序包裹(最外层装饰器最先执行)。
- 每个
Decorator实现里,应显式调用next.Handle(ctx, req),而非直接调用原始 handler —— 否则链路中断 - 如果 handler 方法有返回值(如
resp interface{}),装饰器也需透传该返回值,否则上层拿不到结果 - 避免在装饰器中做阻塞 I/O(如同步写磁盘日志),应异步或用缓冲 channel,否则拖慢整个链路
HTTP 中间件是最典型的装饰器落地场景
标准库 net/http 的中间件本质就是装饰器:接收 http.Handler,返回新的 http.Handler。例如日志中间件:
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("START %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("END %s %s", r.Method, r.URL.Path)
})
}
这里容易踩的坑是:忘记调用 next.ServeHTTP(...) 就直接返回,导致请求被静默吞掉;或者误用 http.HandlerFunc 包裹后又手动调用 next.ServeHTTP,造成重复执行。
- 所有中间件必须调用
next.ServeHTTP,除非有意终止(如鉴权失败写 401 后 return) - 若需改写响应(如加 header、压缩 body),要用
ResponseWriter包装器,而不是直接操作原始w - 中间件顺序敏感:认证应在日志之后、业务 handler 之前;恢复 panic 的中间件应放在最外层
真正难的不是写一个装饰器,而是设计好 handler 接口的输入/输出契约,以及确保所有装饰器对上下文(context.Context)、错误传播、可观测性字段(如 traceID)的处理方式一致。一旦契约松动,叠加三层装饰器后就很难定位是哪一层吞了错误或丢了上下文。










