godecorator, 一个让 Go 语言实现装饰器特性的编译链工具

2023年 8月 22日 25.3k 0

仍在迭代中,仅供测试。请勿应用于生产环境!!!

go-decorator 是实现了 Go 语言装饰器特性的编译链工具。

使用该工具,通过 //go:decor decoratorfunctionName 来注释函数,即可使用装饰器decoratorfunctionName,快速完成样板代码注入、改变函数行为、控制逻辑流程等。

装饰器的使用场景,可以类比其他语言,比如 Python、TypeScript。

go-decorator 是在编译时进行的装饰器注入,因此它不会破坏你项目的源文件,也不会额外在项目中生成新的.go文件和其他多余文件。 和 go:generate 生成式完全不同。

使用样例

创建一个项目,我们这里module名为 demo.

在你的项目引入装饰器依赖(go.mod 项目):

$ go mod init 
$ go get -u github.com/dengsgo/go-decorator

创建 main.go, 编写如下代码:

package main

import (
	"github.com/dengsgo/go-decorator/decor"
	"log"
)

func main() {
	// 正常调用你的函数。
	// 由于这是一个声明使用装饰器logging的函数, 
	// decorator 编译链会在编译代码时注入装饰器方法logging的调用。
	// 所以使用上面的方式编译后运行,你会得到如下输出:
	// 
	// 2023/08/13 20:26:30 decorator function logging in []
	// 2023/08/13 20:26:30 this is a function: myFunc
	// 2023/08/13 20:26:30 decorator function logging out []
	// 
	// 而不是只有 myFunc 本身的一句输出。
	// 也就是说通过装饰器改变了这个方法的行为!
	myFunc() 
}

// 通过使用 go:decor 注释声明该函数将使用装饰器logging来装饰。
//
//go:decor logging
func myFunc() {
	log.Println("this is a function: myFunc")
}

// 这是一个普通的函数
// 但是它实现了 func(*decor.Context) 类型,因此它还是一个装饰器方法,
// 可以在其他函数上使用这个装饰器。
// 在函数中,ctx 是装饰器上下文,可以通过 ctx 获取到目标函数的出入参
// 和目标方法的执行。
// 如果函数中没有执行 ctx.TargetDo(), 那么意味着目标函数不会执行,
// 即使你代码里调用了被装饰的目标函数!这时候,目标函数返回的都是零值。
// 在 ctx.TargetDo() 之前,可以修改 ctx.TargetIn 来改变入参值。
// 在 ctx.TargetDo() 之后,可以修改 ctx.TargetOut 来改变返回值。
// 只能改变出入参的值。不要试图改变他们的类型和数量,这将会引发运行时 panic !!!
func logging(ctx *decor.Context) {
	log.Println("decorator function logging in", ctx.TargetIn)
	ctx.TargetDo()
	log.Println("decorator function logging out", ctx.TargetOut)
}

然后 go install 安装 decorator 工具:

$ go install github.com/dengsgo/go-decorator/cmd/decorator@latest

运行 decorator,显示 decorator 版本信息即为安装成功。

$ decorator
decorator 0.1.0 beta , https://github.com/dengsgo/go-decorator

decoratorgo 的编译链工具,依靠 go 命令来调用它运行,进行代码的编译。

所以我们编译代码需要在 go build 命令中加入 -toolexec decorator 参数。

编译&&运行:

$ go build -toolexec decorator 
$ ./demo

会有类似输出打印:

2023/08/13 20:26:30 decorator function logging in []
2023/08/13 20:26:30 this is a function: myFunc
2023/08/13 20:26:30 decorator function logging out []

说明 go-decorator 已经成功加入了编译链,并注入了装饰器。

详细的使用文档可以访问下面的链接:

Guide | Guide 国内Gitee

项目代码:

GitHub: github.com/dengsgo/go-…

有一些大家可能会关心的点,这里也提一下:

条件和限制

以下几种情况需要注意:

  • 使用装饰器的目标函数范围仅限当前项目内。依赖的其他库即使使用 //go:decor也无法被装饰。

例如,你的项目module名称是 a/b/c ,那么 //go:decor 只在 a/b/c 及其子包中生效(a/b/c/d 有效,a/m/无效)。

但是//go:decor可以使用任意包的装饰器,没有范围限制。

  • 不能在同一个目标函数上同时使用相同的装饰器重复装饰;
  • 不能对装饰器函数应用装饰器;
  • 升级 decorator 后或者调整编译参数可能需要在 go 命令中追加 -a 参数强制编译一次,以覆盖旧的编译缓存。

开发与调试

decorator 作为 go 编译链中的一环,编译时被 go 编译器加载使用。它与 go 的编译链保持兼容,不会产生副作用。

开发流程中要改变的只是给用到的 go 命令增加 -toolexec decorator 参数,其他完全一致,感觉不到有变化。

你也可以随时取消这个参数。放弃项目对 go 装饰器的使用。即使代码中保留了 //go:decor 注释也不会有任何副作用(因为它对于标准工具链来说只是无意义的注释而已)。

调试同理。

例如,在 vscode 中,编辑 launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch file",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${file}",
            "buildFlags": "-toolexec decorator"
        }
    ]
}

添加 "buildFlags": "-toolexec decorator" 这一行以启用 decorator 的装饰器编译。

然后正常断点调试即可。

调试体验会不断完善,如果发现问题请让我知道 Issues。

性能

尽管 decorator 在编译时会对目标函数做额外的处理,但它仅仅只构建必要的上下文参数,没有额外开销,更没有反射。相对于原始go代码直接调用装饰器函数来讲,性能几乎是一致的。

最后

项目初期,可能会遇到各种各样的问题,随时欢迎 Issues 反馈给我, Issues 。

最后最后,强调一下,现阶段不要将它用于生产环境!现阶段不要将它用于生产环境!现阶段不要将它用于生产环境!

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论