本文记录了作者在golang开发中,通过抽取接口,依赖注入的方式,解决包与包之间的不合理引用关系。
总结来说:
面向接口编程,并且golang中接口函数的参数最好是标准库的类型
场景
目前项目中有一个业务逻辑包business_logic
,两个工具库包pkg1
和pkg2
,其中
pkg1
是旧库,API不宜改动,pkg2
是新库,尚未正式使用business_logic
会使用pkg1
和pkg2
pkg1
内部要添加使用pkg2
的逻辑
1 | // pkg1/main.go |
1 | // pkg2/main.go |
1 | // business_logic/main.go |
这样就引起了一个问题:
business_logic
其实引用了两次pkg2
,一次是直接引用,一次是通过pkg1
间接引用,将来在版本更迭中,很有可能会出现直接引用的版本和间接引用的版本不一致的情况,从而引起未知bug
解决尝试
如果不希望两次引用,那么最好的方式是消除pkg1
对pkg2
的引用,消除引用的方式是
pkg1
抽象出一个接口,- 让
pkg2
提供结构体,实现pkg1
抽象出的接口
这样,pkg2
实际上就变成了pkg1
的一个插件,只要在business_logic
初始化的时候,将pkg2
的插件注入到pkg1
里去就行
但是这样的尝试失败了,我们先来看一下代码
1 | // pkg1/main.go |
1 | // pkg2/main.go |
1 | // business_logic/main.go |
我们发现,pkg1
对pkg2
的引用仍旧存在,其原因在于抽取出来的接口函数中的参数是属于pkg2
的
1 | type Plugin interface { |
最终解决方案
由于pkg2
是新库,所以我们决定更改它的接口,最终的代码如下
1 | // pkg1/main.go |
1 | // pkg2/main.go |
1 | // business_logic/main.go |
可以看到,这回彻底解决了pkg1
引用pkg2
的问题,代价就是将pkg2.S
这个结构体参数展开了
视具体业务情况而定,我们可以通过:
- 展开结构体
- 将结构体换做
map[string]interface{}
(当然需要手动做字段的提取和塞入) - 将结构体换做
string
,用JSON传参(手动Marshal和Unmarshal) - 将参数类型放到新的第三方库
pkg3
中(这样就又要维护引用的pkg3
版本一致)
软件开发中没有silver-bullet,只有trade-off,这次的方案,也还算满意