被 Java 毒害的脑子想在 Go 中实现一个操作,望打醒

6 天前
 assiadamo

以前接触过的一个 Java 项目,实现了一种在我看来很新的做法:

  1. 代码生成的协议类,里面自带了一个未实现的 process 方法
public class Echo extend Msg {
	String msg;
  	public void decode(){}
  	public void encode(){}
  	public void process() throws Exception {
  		throw new UnsupportedOperationException();
  	}
}
  1. 代码生成的协议处理类,格式是这样的
@MsgProcess
public static boolean process(Echo echo) {
	return true;
}
  1. 框架启动的时候,会反射获取到注解@MsgProcess的 Metchod 和他的参数,然后用 javaassist 的字节码操作,将协议类Echoprocess方法给替换掉!这样框架层调用协议的msg.process()就可以直接执行业务逻辑!

Java 写了 10 年,一说起框架,自然想到的就是各种设计模式抽象继承与反射之类,当写 Go 的时候,也受到影响,我现在想用 Go 实现类似的操作,实践的效果如下

  1. 代码生成了 Echo 协议类
package proto
type Echo struct {
	BaseMsg
	Msg string
}
func (msg *Echo) Decode(src *bytes.Buffer) error {}
func (msg *Echo) Encode(dst *bytes.Buffer) error {}
func (msg *Echo) Process() {
	panic("implement me")
}
  1. 代码生成了业务逻辑类
package logic
import proto
func ProcessEcho(msg *proto.Echo) {}
  1. 使用 ast/parser 将Echoprocess的方法体替换为ProcessEcho
func (msg *Echo) Process() {
	logic.ProcessEcho(msg)
}

但重新生成的 Echo 类,有一些问题,首先生成出来的文件,我将其保存为echo_override.go放在另一个 package ,相关的 import 都可能有问题,然后Processimport 了 logic ,而 logic 自然要 import echo ,非常经典的 import cycle 。

这是第一步遇到的问题,我打算先用 interface 解决看看,为什么不用 func 替换,我觉得好丑啊!各位 Go 大神有没有什么建议?我这种思路,符合 Go 的设计哲学吗?

5928 次点击
所在节点    Go 编程语言
68 条回复
NessajCN
6 天前
在我看来只需要定义
func Process(msg *Echo)就够了,再来个 ProcessEcho 意义何在
yolee599
6 天前
我是不喜欢反射,反射会让代码变得不可预测
assiadamo
6 天前
@NessajCN 因为不要去 proto 包下生成的协议类中写业务逻辑,想把两边的编码隔离开
cookii
6 天前
@yolee599 同不喜欢反射,滥用反射的场景太多了。
zjsxwc
6 天前
把 echo.go 改名成 echo.go.proto ,把 echo_override.go 改名成 echo.go 呗
NessajCN
6 天前
@assiadamo 那就直接在 logic 里定义 Process 啊,proto 那里那个不用了呗
assiadamo
6 天前
@zjsxwc 有点天才,可以一试
assiadamo
6 天前
@NessajCN 框架想做到不接触业务层,直接调用协议的`msg.Process()`就能执行业务逻辑,否则还要手动将业务层的各种协议处理函数注册一下
NessajCN
6 天前
@assiadamo 你一边说不想在框架里写业务,一边又说直接调用框架里的方法执行业务逻辑,你到底想说啥?
to2false
6 天前
proto 生成的是 interface

自己实现这个 interface 不就好了
assiadamo
6 天前
@NessajCN 这就是想要做到的魔法
assiadamo
6 天前
@to2false Go 的 interface 不能放成员变量就很可惜,比如协议内数据定义,Decode/Encode 相关的代码都不想出现在业务层,要想用 interface 就要再搞个 BaseEcho 之类的组合,还是觉得有点丑
NessajCN
6 天前
@assiadamo 所以你的意思让框架猜你的业务逻辑?
assiadamo
6 天前
@NessajCN Java 有注解和字节码替换,可以批量的处理一系列相同定义的东西,但 Go 如果没有相关的魔法,就要手动的注册函数,比如经典的 HandleFunc(path, func(){}),这样在玩具中,或者接口比较少的项目中可以手动,但如果有几百上千种协议定义呢?
当然可以借用代码生成,比如再生成一个 handle.go ,我想省去这个文件
zerozerone
6 天前
NessajCN
6 天前
@assiadamo 我试着理解你的意图:你是不是想定义一个 func Process() , 这个 Process 的参数可以是 Echo 类型的量,也是跟 Echo 类似的还有几百上千个的其他结构体的变量,你不想为这些结构体里一个一个定义 Process 函数?
povsister
6 天前
你既然只关心 Process ,那直接泛型不就完事了。
很常见的设计模式,go 处理 wiredata 简直舒服的不要不要的,泛型前写这种协议代码还脏一点 interface 乱飞。现在直接给你类型检查都安排的明明白白 。
多练,多看。

业务只需实现 ProcessMsg ,然后直接业务代码自己 Register 就行了。

assiadamo
6 天前
@NessajCN 不是一个
Echo 协议类生成的时候,会伴随着在业务项目中生成一个
```
func Process 协议名(协议){}
```
这样其实当协议收取的时候,框架就应该知道对应的处理函数是什么,信息完全是足够的,但需要一个方法让协议内部的 Process 方法和业务对应协议 Prosess 方法联动起来,我想这一步让框架自己处理,而不是手动的去配置联动关系
povsister
6 天前
补充楼上,你顺带也可以把 Constraint 扩展一下加个 Name 方法,这样直接 struct 定义业务消息处理和业务消息类型,框架负责序列化反序列化。
更多一点,通过提供不同入参的 register 方法,或者变长参数提供 register option ,还可以实现业务可选定义 encode decode ,或者直接将整个 codec 层可插拔可协商,非常简洁明了。
NessajCN
6 天前
@assiadamo
那我基本确定了,你可能没太理解 interface 的本质
Go 里的 interface 就是为你说的这种应用场景准备的
你不需要给每个协议都生成一个 Process ,
只需要定义个 interface ,再定义单独一个 Process ,用这个 interface 当参数类型
然后在框架里给每个协议定义好 interface 需要用到的方法就行

这样就是业务完全不需要管框架里怎么实现 Process , 直接定义一个协议结构体变量,然后 Process 它即可

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1095483

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX