Go中无装饰器语法,但可通过结构体嵌入(尤其指针嵌入)模拟装饰器模式:嵌入原类型并重写方法以增强行为;需统一接口、避免nil指针、注意初始化顺序与生命周期。
Go 语言本身不支持 Python 那种 @decorator 语法,也没有 Java 的注解 + AOP 框架。所谓“装饰器模式”在 Go 中本质是:**用组合代替继承,通过嵌入(embedding)已有类型,再覆盖或扩展其方法**。关键不是名字,而是能否在不修改原类型的前提下,动态添加职责(比如加日志、重试、熔断)。
要让装饰行为对调用方无感,必须满足:被装饰类型的方法必须出现在装饰器的可导出方法集中。这取决于嵌入方式:
struct{} 或 *struct{} —— 前者嵌入值类型,后者嵌入指针类型;方法集差异直接影响能否调用指针接收者方法Foo 只有指针接收者方法(如 (*Foo).Do()),则必须嵌入 *Foo,否则 DecoratedFoo.Do() 编译失败d.Foo.Do()
type Foo struct{}
func (*Foo) Do() string { return "original" }
type LoggingFoo struct {
*Foo // 必须是指针嵌入,否则无法调用 *Foo.Do()
}
func (l *LoggingFoo) Do() string {
fmt.Println("before Do")
result := l.Foo.Do()
fmt.Println("after Do")
return result
}
Python 装饰器可以堆叠写成 @log @retry @cache,Go 没有这种语法糖。多个装饰逻辑只能靠手动
嵌套构造:
new(RetryFoo).Wrap(new(CacheFoo).Wrap(new(LoggingFoo).Wrap(&RealService{}))) 这类写法易错且难读Service interface{ Do() string }),所有装饰器和原始实现都实现它CacheService,再包一层 RetryService,最后赋给接口变量type Service interface {
Do() string
}
type CacheService struct {
next Service // 接口,非具体类型
}
func (c *CacheService) Do() string {
// 先查缓存...
return c.next.Do() // 调用下一层
}
结构体嵌入后,如果装饰器字段未显式初始化,会触发零值行为,常见陷阱:
立即学习“go语言免费学习笔记(深入)”;
*Foo),但没赋值就调用其方法 → panic: nil pointer dereferenceFoo),但 Foo 自身含不可复制字段(如 sync.Mutex)→ 编译错误
真正难的不是写一个装饰器,而是确保整条链上每个环节都正确处理了嵌入字段生命周期、错误传播、上下文传递——这些细节不会报编译错误,但会在压测时突然暴露。