GO语言装饰器语法

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 的函数类型、闭包和高阶函数,把原来的函数包装起来,在不修改原函数的情况下增加新的功能。

相关推荐
金金计较.7 小时前
Go语言-4
开发语言·golang
名字还没想好☜19 小时前
Go context.AfterFunc 实战(Go 1.21):context 一取消就自动跑清理,告别手写 goroutine 监听 Done
后端·golang·go
web守墓人19 小时前
【goed/ui】自定义组件设计思想篇
linux·windows·ui·golang
chenqianghqu2 天前
go语言SDK升级到1.27更新调试
golang
ttwuai2 天前
Go开源后台管理系统推荐:怎么先排除 Fork、镜像和同名项目?
golang
参.商.3 天前
【Day 53】76. 最小覆盖子串
leetcode·golang
传奇开心果编程3 天前
【Go入门练中学】第2课:条件与循环
后端·学习·golang
传奇开心果编程3 天前
【Go入门练中学】第1课:从零开始
后端·学习·golang
报错小能手3 天前
GO语言接口 泛型 错误处理
golang