golang抽取接口,依赖注入(依赖倒置)解决包引用关系

本文首发于我的个人博客
本文记录了作者在golang开发中,通过抽取接口,依赖注入的方式,解决包与包之间的不合理引用关系。

总结来说:

面向接口编程,并且golang中接口函数的参数最好是标准库的类型

场景

目前项目中有一个业务逻辑包business_logic,两个工具库包pkg1pkg2,其中

  • pkg1是旧库,API不宜改动,pkg2是新库,尚未正式使用
  • business_logic会使用pkg1pkg2
  • pkg1内部要添加使用pkg2的逻辑
// pkg1/main.go
package pkg1

import "pkg2"

func ExternalAPI() {
    pkg2.ExternalAPI(pkg2.S{})
}

// pkg2/main.go
package pkg2

type S struct {
    param1 int
}

func ExternalAPI(s S) {
}
// business_logic/main.go
package main

import (
    "pkg1"
    "pkg2"
)

func main() {
    pkg1.ExternalAPI()
    pkg2.ExternalAPI(pkg2.S{})
}

这样就引起了一个问题:

business_logic其实引用了两次pkg2,一次是直接引用,一次是通过pkg1间接引用,将来在版本更迭中,很有可能会出现直接引用的版本和间接引用的版本不一致的情况,从而引起未知bug

解决尝试

如果不希望两次引用,那么最好的方式是消除pkg1pkg2的引用,消除引用的方式是

  • pkg1抽象出一个接口,
  • pkg2提供结构体,实现pkg1抽象出的接口

这样,pkg2实际上就变成了pkg1的一个插件,只要在business_logic初始化的时候,将pkg2的插件注入到pkg1里去就行

但是这样的尝试失败了,我们先来看一下代码

// pkg1/main.go
package pkg1

import "pkg2"

type Plugin interface {
    ExternalAPI(s pkg2.S)
}

var plugin Plugin

func ExternalAPI() {
    if plugin != nil {
        plugin.ExternalAPI(pkg2.S{})
    }
}

func SetPlugin(p Plugin) {
    plugin = p
}
// pkg2/main.go
package pkg2

type S struct {
    param1 int
}

type Plugin struct {
}

func (p *Plugin) ExternalAPI(s S) {
}

func ExternalAPI(s S) {
    p := Plugin{}
    p.ExternalAPI(s)
}
// business_logic/main.go
package main

import (
    "pkg1"
    "pkg2"
)

func main() {
    pkg1.SetPlugin(&pkg2.Plugin{})
    pkg1.ExternalAPI()
    pkg2.ExternalAPI(pkg2.S{})
}

我们发现,pkg1pkg2的引用仍旧存在,其原因在于抽取出来的接口函数中的参数是属于pkg2

type Plugin interface {
    ExternalAPI(s pkg2.S)
}

最终解决方案

由于pkg2是新库,所以我们决定更改它的接口,最终的代码如下

// pkg1/main.go
package pkg1

type Plugin interface {
    ExternalAPI(param int)
}

var plugin Plugin

func ExternalAPI() {
    if plugin != nil {
        plugin.ExternalAPI(0)
    }
}

func SetPlugin(p Plugin) {
    plugin = p
}
// pkg2/main.go
package pkg2

type Plugin struct {
}

func (p *Plugin) ExternalAPI(s int) {
}

func ExternalAPI(s int) {
    p := Plugin{}
    p.ExternalAPI(s)
}
// business_logic/main.go
package main

import (
    "pkg1"
    "pkg2"
)

func main() {
    pkg1.SetPlugin(&pkg2.Plugin{})
    pkg1.ExternalAPI()
    pkg2.ExternalAPI(0)
}

可以看到,这回彻底解决了pkg1引用pkg2的问题,代价就是将pkg2.S这个结构体参数展开了

视具体业务情况而定,我们可以通过:

  1. 展开结构体
  2. 将结构体换做map[string]interface{}(当然需要手动做字段的提取和塞入)
  3. 将结构体换做string,用JSON传参(手动Marshal和Unmarshal)
  4. 将参数类型放到新的第三方库pkg3中(这样就又要维护引用的pkg3版本一致)

软件开发中没有silver-bullet,只有trade-off,这次的方案,也还算满意

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值