Go中无原生decorator语法,但可通过函数值与闭包模拟:装饰器接收被装饰函数为参数,返回同签名函数,在闭包中执行前置/后置逻辑,如HTTP中间件或业务层重试、鉴权等。

Go里没有原生decorator语法,但能用函数值+闭包模拟
Go语言不支持Python那种@decorator语法糖,也不允许在函数定义时直接套装饰逻辑。但它的函数是一等公民,func(http.Handler)、func(context.Context, interface{}) error这类签名天然适合“包装”——把原始业务逻辑当参数传进去,在前后插入通用处理,就是装饰器的本质。
常见误操作是试图用结构体方法强行模仿类装饰器,结果写一堆DecoratedHandler、WithAuth嵌套调用,反而让调用链变深、类型变复杂。正确做法是保持函数签名一致,靠闭包捕获中间件参数,返回新函数。
- 装饰器函数必须接收「被装饰的函数」作为参数,且返回同签名函数
- 闭包内做前置/后置逻辑,比如记录耗时、注入ctx、校验token
- 避免在装饰器里修改原始函数的入参或返回值结构,否则下游难测试
用http.HandlerFunc实现HTTP中间件是最典型场景
HTTP handler是Go中装饰器落地最成熟的例子。func(http.ResponseWriter, *http.Request)签名统一,中间件如loggingMiddleware、authMiddleware都返回http.HandlerFunc,可链式调用。
关键点在于:中间件函数本身不处理请求,它只返回一个handler;真正的执行发生在http.ServeMux路由分发时。
立即学习“go语言免费学习笔记(深入)”;
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r) // 调用下游handler
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
}
}
func authMiddleware(requiredRole string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
role := r.Header.Get("X-Role")
if role != requiredRole {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next(w, r)
}
}
}
-
authMiddleware是“装饰器工厂”,先传配置(requiredRole),再传目标handler - 链式写法:
http.HandleFunc("/admin", authMiddleware("admin")(loggingMiddleware(adminHandler))) - 注意调用顺序:最外层的装饰器最先执行,所以
loggingMiddleware会统计包含鉴权在内的总耗时
业务逻辑层用func(context.Context, *Request) (*Response, error)更可控
HTTP层之外,业务服务接口常定义为接受context.Context和请求结构体,返回响应和错误。这种签名比裸http.Handler更利于单元测试,也更适合加事务、重试、熔断等装饰逻辑。
陷阱是直接在装饰器里ctx = context.WithValue(...)然后传给下游——如果多个装饰器都往ctx塞key,容易冲突或覆盖。应优先用结构体字段传递上下文数据,或约定key类型为私有指针。
- 重试装饰器示例:
func withRetry(maxRetries int) func(HandlerFunc) HandlerFunc,内部用for i := 0; i 循环调用 - 事务装饰器需判断是否已存在
tx上下文,避免嵌套开启新事务 - 不要在装饰器里panic,应统一转为
errors.Wrap或自定义error type,方便下游分类处理
别把装饰器当万能胶,警惕过度抽象和性能损耗
每个装饰器都是一次函数调用+闭包变量捕获,10层嵌套意味着10次额外栈帧和10次间接跳转。线上高频接口(如用户查询)若叠加日志、指标、缓存、鉴权、审计5个装饰器,实测QPS可能下降15%~20%。
真正该被装饰的,是那些「与核心业务无关但必须执行」的横切关注点。比如订单创建流程里,扣库存、发消息、更新状态是主干,而「记录操作日志」和「触发风控检查」才适合抽成装饰器。
- 优先用组合而非装饰:比如
OrderService结构体里直接嵌入logger、cacheClient字段,比层层wrap更直观 - 调试时用
runtime.Caller(1)在装饰器里打点,确认实际调用栈深度 - 生成代码工具(如
go:generate)比手动写装饰器更适合固定模式,比如gRPC拦截器自动生成
装饰器不是设计模式教科书里的摆设,它是函数式思维在Go里的务实落地——用最少的语法成本,把重复逻辑从if/else泥潭里拎出来。写的时候多问一句:这个逻辑下次出现时,我愿不愿意复制粘贴?如果答案是否定的,那它就该被装饰。


















