如何在Golang中构建装饰器模式_Golang装饰器模式功能扩展技巧

10次阅读

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

如何在Golang中构建装饰器模式_Golang装饰器模式功能扩展技巧

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 包装

装饰器链越长,ctx 传递、error 分类、panic 捕获、响应体序列化这些细节就越容易出错。与其手动拼接,不如早期就确定是否真需要运行时动态组合——很多时候,一个结构体字段标记 + switch 分支更清晰、更可控。

text=ZqhQzanResources