最近开发项目的时候,遇到了一个go里面很不好解决的问题,循环引用。

为了方便展示,我将整个项目抽象了下,项目结果如下
structure
项目的错误如下
error
包A中的代码如下

package A

import (
    "strings"
    "github.com/hundred666/GoTest/B"
)

func Foo(a string) (string) {
    return B.Add(a)
}

func Minus(a string) (string) {
    return strings.Trim(a, "\t")
}

包B中的代码如下

package B

import "github.com/hundred666/GoTest/A"

func Goo(a string) (string) {
    return A.Minus(a)
}

func Add(a string) (string) {
    return a + "----"
}

主函数代码如下

package main

import (
    "github.com/hundred666/GoTest/A"
)

func main() {
    A.Foo("good")
}

就是单纯的用了两个包里面的函数,就不让用!
在网上搜解决方案,主要有以下几个办法

设计整个项目结构

在写这些代码之前,就设计好项目的结构,避免出现这些情况。
这个是最应该做的办法,可是代码都写了这么多了再从头写一遍很不合理,而且这种需要一个对整个项目把控能力很强的架构师来做,我还到不了这么高的水平。

引入第三方包

我们可以看到,循环依赖只是对立面一些函数的依赖,所以,如果我们将这些函数单独抽象出去的话,也是一种解决办法。大体的文档结构就是

main

A +------------>Foo

B +------------>Goo

C +------------->Add()
  |
  +------------->Minus()

就是将A,B包里面的共用函数单独封成第三个包,A,B调用C的包
不可否认,这个方法会解决一些问题,可是肯定不是最优的解决办法。
因为我们这的函数比较简单,如果加减函数需要使用包里面的一些变量,可如何解决?
所以这个方法也不是非常合适。

函数作为参数传递

这个是一个比较有意思的解决方案,我们可以看到A中的Foo函数引用了B中的Add函数,我们可以将Add函数作为参数传递给A,A再进行调用

func Foo(a string, f func(string)(string))(string){
    return f(a)
}

main里面需要改成这样

r := A.Foo("good", B.Add)

运行结果正确,问题得解。
可是这样的话,就把难度全部放到了调用的函数那,依然具有很高的耦合度,不利于项目的开发,还有没有其他的解决办法?

使用外观模式

我们之前的java设计模式中介绍到了外观模式,发现这在很有用
我首先将包A,B中的方法抽象成接口,将方法先隔离出来

package service

type A interface {
    Minus(s string) (string)
}

type B interface {
    Add(s string) (string)
}

然后我A,B实现接口。为了容易处理,我不放定义两个结构体进行处理。

package A

import (
    "strings"
    "github.com/hundred666/GoTest/service"
)

type AImpl struct {
    b service.B
}

func (a *AImpl) Foo(s string) (string) {
    return a.b.Add(s)
}

func (a *AImpl) Minus(s string) (string) {
    return strings.Trim(s, "\t")
}

B的设计如下

package B

import "github.com/hundred666/GoTest/service"

type BImpl struct {
    a service.A
}

func (b *BImpl) Goo(a string) (string) {
    return b.a.Minus(a)
}

func (b *BImpl) Add(a string) (string) {
    return a + "----"
}

实现了方法,得能够将实例化的变量分别放入A,B结构体中,因此A需要实现以下方法

func NewA() *AImpl {
    return new(AImpl)
}
func (a *AImpl) SetB(b service.B) {
    a.b = b
}

B需要实现以下方法

func NewB() *BImpl {
    return new(BImpl)
}

func (b *BImpl) SetA(a service.A) {
    b.a = a
}

这样就完成了整个的设计,需要调用的时候可以这样调用

package main

import (
    "github.com/hundred666/GoTest/B"
    "github.com/hundred666/GoTest/A"
    "fmt"
)

func main() {
    b := B.NewB()
    a := A.NewA()
    a.SetB(b)
    r := a.Foo("aa")
    fmt.Println(r)
}

好熟悉的java的味道!

结论

项目最好是不要出现循环依赖的,出现了大部分情况都是由于项目结构设计不合理导致的,因此,最好在设计项目的时候就考虑好每一层的功能。

参考文献

  1. golang解决依赖循环问题...
  2. golang不允许循环import问题...
  3. 如何解耦循环依赖
  4. golang包循环依赖问题...
  5. golang package循环依赖的问题

设计模式 golang 循环依赖

发表新评论