go语言无原生@decorator语法,需用高阶函数链式组合或接口+结构体包装实现横切逻辑剥离;必须统一context-aware签名如handlerfunc,严格透传ctx与错误,顺序决定执行流,禁用反射与代码生成。

Go 语言没有 @decorator 语法,但业务逻辑增强必须做——关键不是“怎么模拟 Python”,而是用 func 链或接口包装把日志、超时、权限这些横切逻辑从核心流程里剥离开,且不牺牲类型安全和可调试性。
用 HandlerFunc 统一签名实现链式装饰
所有要被装饰的业务函数必须收敛到一个可组合的类型,否则嵌套会立刻崩掉。最常用的是带 context.Context 和明确输入输出的函数类型:
-
type HandlerFunc func(ctx context.Context, req interface{}) (interface{}, error)—— 这是大多数服务层装饰器的起点 - 不要用
func()或func(interface{}) error:前者无法传递取消信号,后者丢失上下文生命周期控制 - 每个装饰器返回新
HandlerFunc,内部必须调用next(ctx, req),漏传ctx就等于废掉超时和值传递 - 链式顺序决定执行流:
WithLogging(WithTimeout(5*time.Second)(business))中,WithLogging最先执行、最后返回
结构体包装比闭包更适合复杂状态管理
当装饰逻辑需要持状态(如重试计数、熔断器状态、JWT 解析缓存),纯闭包难维护,结构体 + 接口更清晰:
- 定义接口:
type BookingService interface{ Reserve(ctx context.Context, req *ReserveReq) (*ReserveResp, error) } - 装饰器结构体嵌入该接口:
type RetryingBookingService struct{ svc BookingService } - 方法实现里显式调用
s.svc.Reserve(ctx, req),而不是闭包捕获的某个变量名;避免循环引用的关键是别在结构体里存自己指针 - 叠加多个装饰器时,构造顺序即执行顺序:
NewMetricsService(NewAuthzService(baseSvc, policy), metrics)
事务、权限等强语义逻辑必须显式控制边界
这类逻辑不能靠“包装后自动执行”,必须暴露开始/结束点,否则回滚失败或校验绕过就是线上事故:
- 事务装饰器应接收
func(*sql.Tx) error类型,由WithTx负责Begin/Commit/Rollback及 panic 捕获,而不是包装func(context.Context) error - 权限校验必须在调用业务逻辑前完成,失败直接返回错误,不调用
next;成功才透传原始req,而非修改后副本 - 所有错误必须用
fmt.Errorf("xxx: %w", err)包装,否则errors.Is和errors.As在上层失效 - HTTP handler 里包整个事务是反模式——应拆到最小原子操作,比如
CreateOrder和ChargePayment各自独立事务
别碰反射和代码生成
看似省事,实则埋雷:
-
reflect.Value.Call调用性能差,且 IDE 无法跳转到被装饰函数,静态检查失效 - 用
go:generate生成包装代码,每次改接口都要重新运行命令,CI 构建链变长,diff 里全是噪音 - panic 堆栈里出现
reflect.Value.call就意味着你失去了第一手调用链,排查耗时翻倍 - Go 社区共识是“显式优于隐式”——写三行
h.next.Reserve(ctx, req)比藏在工具链里可靠得多
真正容易被忽略的不是怎么写装饰器,而是谁来决定哪一层加什么装饰器。HTTP 层加日志,service 层加事务,domain 层加领域规则校验——边界模糊时,装饰器反而成为耦合源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











