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

相关推荐
紧跟先前的步伐2 小时前
Go AI Agent 实战 22|完善 Agent 执行循环:从工具调用到最终回答
人工智能·ai·golang
后台模板学习3 小时前
Go Web开发中任务调度框架的源码解析与高可用实现方案
开发语言·后端·golang
FungLeo4 小时前
成为全栈·Go 后端篇·分类树与标签关系:目录变更为何需要协调
golang·go·gorm·数据库事务·分类树·成为全栈
fly-iot4 小时前
【FDE知识库】(1):AI 编程时代开发语言选型分析,云使用go,企业用java,算法python,底层c
开发语言·人工智能·golang
FungLeo4 小时前
成为全栈·Go 后端篇·文章查询与展示投影:安全分页、排序和软删除
golang·go·gorm·分页查询·成为全栈·sql安全
紧跟先前的步伐13 小时前
Go AI Agent 实战 24|工具系统工程化收尾与阶段验收
人工智能·ai·golang·1024程序员节
日日大王学编程14 小时前
#Feed 流项目复盘:Redis 时间线缓存与 Outbox 消息可靠性设计
golang·1024程序员节
北冥you鱼21 小时前
Go 语言 CRUD 实战:从接口设计到真实业务场景
开发语言·后端·golang
利威尔xu1 天前
Go IM 源码实战(11):token 只是个 HASH,多端互踢的顺序错了就踢错人
后端·微服务·面试·架构·golang
microrain1 天前
SagooIoT 把协议解析和消息通知都放进了独立进程
物联网·golang·插件·sagooiot