Go中无装饰器语法,需用高阶函数模拟:定义HandlerFunc类型,通过闭包包装实现前置/后置逻辑;装饰器组合为从外到内执行,如WithTimeout(WithRecovery(WithLogging(handler)));HTTP场景需适配器转换签名,注意ctx传递、响应写入限制及panic捕获顺序。

Go 里没有装饰器语法,但可以用函数值模拟
Go 语言本身不支持 Python 那种 @decorator 语法糖,也没有类方法装饰器的概念。所谓“装饰器模式”在 Go 中实际是靠高阶函数(接收函数并返回函数)+ 接口组合 + 中间件式链式调用实现的。核心思路是:把原始逻辑封装为 func(ctx Context, req interface{}) (interface{}, error) 类型,再用闭包包装它,插入前置/后置逻辑。
用函数类型定义统一的装饰器签名
避免每次写装饰器都重复定义参数列表,先约定一个通用类型:
type HandlerFunc func(context.Context, interface{}) (interface{}, error)
所有装饰器都接收并返回这个类型。例如日志装饰器:
func WithLogging(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, req interface{}) (interface{}, error) {
log.Printf("start: %T %+v", req, req)
defer log.Printf("done")
return next(ctx, req)
}
}
- 必须保留原始
ctx传递,否则 cancel、timeout、value 会丢失 - 不要在装饰器里直接
return nil, err后就结束——除非你明确要拦截;否则应始终调用next(...) - 如果需要修改
req或返回值,得考虑深拷贝,否则可能影响下游逻辑
多层装饰器组合时注意执行顺序
Go 的装饰器是“从外到内”嵌套的,WithRecovery(WithLogging(handler)) 表示先执行 WithRecovery 的前置逻辑,再进 WithLogging,最后才到 handler;而错误恢复(recover)必须在外层,否则 panic 无法被捕获。
立即学习“go语言免费学习笔记(深入)”;
-
WithTimeout必须比WithLogging更外层,才能控制整个链路超时 -
WithContextValue类装饰器(往 ctx 塞 key-value)应尽量靠近 handler,避免被中间层覆盖 - 用变量链式赋值易读性差,推荐用一行调用:
final := WithTimeout(WithRecovery(WithLogging(handler)))
HTTP handler 场景下别直接套用通用 HandlerFunc
标准 http.HandlerFunc 签名是 func(http.ResponseWriter, *http.Request),和上面的 HandlerFunc 不兼容。强行转换需适配器:
func HTTPHandlerAdapter(h HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
resp, err := h(r.Context(), r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 将 resp 写回 w,具体取决于 resp 类型
json.NewEncoder(w).Encode(resp)
}
}
这里容易忽略的关键点:
-
http.Request是可变对象,多个装饰器共享同一实例,修改r.URL.Path等字段会影响后续装饰器 - 响应写入
http.ResponseWriter只能一次,WithLogging如果尝试提前WriteHeader会导致 panic - 真实项目中建议用
github.com/go-chi/chi/v5这类路由库,它的Middlewares已处理好顺序与 ResponseWriter 包装










