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

总结来说:

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

场景

business_logicpkg1pkg2
pkg1pkg2business_logicpkg1pkg2pkg1pkg2
// 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_logicpkg2pkg1

解决尝试

pkg1pkg2
pkg1pkg2pkg1
pkg2pkg1business_logicpkg2pkg1

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

// 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{})
}
pkg1pkg2pkg2
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)
}
pkg1pkg2pkg2.S

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

map[string]interface{}stringpkg3pkg3

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