Go 没有 Python 那种 @decorator 语法,但可以通过函数作为参数、函数作为返回值和闭包实现类似的装饰器效果。
装饰器解决的问题很简单:
在不修改原函数的情况下,为原函数增加新的功能。
还是以宠物寄养为例。我们先定义宠物的基本信息:
type BasePet struct {
Name string
DailyCost float64
}
不同类型的宠物通过结构体嵌入复用这些公共信息:
type Dog struct {
BasePet
}
type Cat struct {
BasePet
}
type Rabbit struct {
BasePet
}
同时定义 Pet 接口,让不同的宠物都能够按照统一的方式计算费用:
type Pet interface {
GetName() string
GetCost(days int) float64
}
例如:
func (b BasePet) GetName() string {
return b.Name
}
func (b BasePet) GetCost(days int) float64 {
return float64(days) * b.DailyCost
}
这样宠物本身只负责自己的基础费用计算。
比如:
dog := Dog{
BasePet{
Name: "大黄",
DailyCost: 50,
},
}
计算 5 天的费用:
dog.GetCost(5)
得到:
250
现在有一个新的需求:所有宠物都需要增加一个额外的费用处理规则。
如果直接修改 GetCost():
func (b BasePet) GetCost(days int) float64 {
cost := float64(days) * b.DailyCost
return cost * 0.9
}
虽然可以实现,但这样就把"基础费用计算"和"额外费用处理"写到了一起。
以后如果又需要增加:
打折
会员优惠
喂食费用
节假日费用
就会不断修改原来的 GetCost()。
这时候可以把额外功能单独拿出来,这就是装饰器的思路。
先定义一个装饰器:
func AddFood(fn func() float64) func() float64 {
return func() float64 {
return fn() * 0.9
}
}
这个函数最重要的地方不是里面的 0.9,而是它的结构:
func AddFood(fn func() float64) func() float64
它表示:
接收一个函数
↓
对这个函数进行包装
↓
返回一个新的函数
也就是说,原来的函数:
func() float64
经过 AddFood 后,还是得到:
func() float64
所以可以继续调用。
例如:
fn := func() float64 {
return dog.GetCost(5)
}
newFn := AddFood(fn)
cost := newFn()
这里的变化就是:
原来的 fn
↓
AddFood(fn)
↓
新的 newFn
↓
调用 newFn()
原来的函数负责:
计算基础费用
装饰器负责:
对基础费用进行额外处理
两部分互不影响。
这也是装饰器最核心的思想:
原功能
↓
装饰
↓
新功能
在实际代码中,可以把它写得更紧凑:
cost := AddFood(func() float64 {
return dog.GetCost(5)
})()
这里实际上就是把:
func() float64 {
return dog.GetCost(5)
}
作为原函数传给 AddFood。
AddFood 返回一个新的函数,最后的 () 再把这个新函数调用起来。
如果觉得这种写法不容易理解,可以拆开:
fn := func() float64 {
return dog.GetCost(5)
}
newFn := AddFood(fn)
cost := newFn()
学习装饰器的时候,推荐先使用这种写法,更容易理解函数之间的关系。
装饰器的另一个特点是可以叠加。
例如现在除了 AddFood,又增加一个折扣装饰器:
func Discount(fn func() float64) func() float64 {
return func() float64 {
return fn() * 0.8
}
}
那么就可以:
fn := func() float64 {
return dog.GetCost(5)
}
fn = AddFood(fn)
fn = Discount(fn)
cost := fn()
执行过程变成:
dog.GetCost(5)
↓
AddFood
↓
× 0.9
↓
Discount
↓
× 0.8
↓
最终费用
这样增加功能时,不需要不断修改 GetCost(),只需要增加新的装饰器。
例如:
fn = AddFood(fn)
fn = Discount(fn)
fn = SomeOtherDecorator(fn)
这也是装饰器相比直接修改原函数更灵活的地方。
在这个宠物寄养的例子中,可以把各部分职责分开:
BasePet
↓
保存宠物基本信息
GetCost()
↓
计算基础寄养费用
AddFood()
↓
增加额外处理
Discount()
↓
增加折扣处理
PetStore
↓
负责使用这些功能
最终形成的是一种"组合功能"的方式,而不是把所有逻辑都塞进一个函数。
所以 Go 中学习装饰器,重点不是记住某一个固定的函数写法,而是理解下面这个关系:
函数作为参数
↓
函数作为返回值
↓
闭包
↓
函数包装
↓
装饰器
最基本的装饰器结构可以记成:
func Decorator(fn func()) func() {
return func() {
// 新增功能
fn()
// 新增功能
}
}
对于带返回值的函数:
func Decorator(fn func() float64) func() float64 {
return func() float64 {
// 新增功能
result := fn()
// 修改或处理 result
return result
}
}
这就是 Go 中比较常见的装饰器写法。
它的核心不是某个具体的"装饰器类",而是利用 Go 的函数类型、闭包和高阶函数,把原来的函数包装起来,在不修改原函数的情况下增加新的功能。