【注意】最后更新于 July 14, 2018,文中内容可能已过时,请谨慎使用。
最近开发项目的时候,遇到了一个go里面很不好解决的问题,循环引用。
为了方便展示,我将整个项目抽象了下,项目结果如下
项目的错误如下
包A中的代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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中的代码如下
1
2
3
4
5
6
7
8
9
10
11
|
package B
import "github.com/hundred666/GoTest/A"
func Goo(a string) (string) {
return A.Minus(a)
}
func Add(a string) (string) {
return a + "----"
}
|
主函数代码如下
1
2
3
4
5
6
7
8
9
|
package main
import (
"github.com/hundred666/GoTest/A"
)
func main() {
A.Foo("good")
}
|
就是单纯的用了两个包里面的函数,就不让用!
在网上搜解决方案,主要有以下几个办法
设计整个项目结构
在写这些代码之前,就设计好项目的结构,避免出现这些情况。
这个是最应该做的办法,可是代码都写了这么多了再从头写一遍很不合理,而且这种需要一个对整个项目把控能力很强的架构师来做,我还到不了这么高的水平。
引入第三方包
我们可以看到,循环依赖只是对立面一些函数的依赖,所以,如果我们将这些函数单独抽象出去的话,也是一种解决办法。大体的文档结构就是
1
2
3
4
5
6
7
8
9
|
main
A +------------>Foo
B +------------>Goo
C +------------->Add()
|
+------------->Minus()
|
就是将A,B包里面的共用函数单独封成第三个包,A,B调用C的包
不可否认,这个方法会解决一些问题,可是肯定不是最优的解决办法。
因为我们这的函数比较简单,如果加减函数需要使用包里面的一些变量,可如何解决?
所以这个方法也不是非常合适。
函数作为参数传递
这个是一个比较有意思的解决方案,我们可以看到A中的Foo函数引用了B中的Add函数,我们可以将Add函数作为参数传递给A,A再进行调用
1
2
3
|
func Foo(a string, f func(string)(string))(string){
return f(a)
}
|
main里面需要改成这样
1
|
r := A.Foo("good", B.Add)
|
运行结果正确,问题得解。
可是这样的话,就把难度全部放到了调用的函数那,依然具有很高的耦合度,不利于项目的开发,还有没有其他的解决办法?
使用外观模式
我们之前的java设计模式中介绍到了外观模式,发现这在很有用
我首先将包A,B中的方法抽象成接口,将方法先隔离出来
1
2
3
4
5
6
7
8
9
|
package service
type A interface {
Minus(s string) (string)
}
type B interface {
Add(s string) (string)
}
|
然后我A,B实现接口。为了容易处理,我不放定义两个结构体进行处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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的设计如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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需要实现以下方法
1
2
3
4
5
6
|
func NewA() *AImpl {
return new(AImpl)
}
func (a *AImpl) SetB(b service.B) {
a.b = b
}
|
B需要实现以下方法
1
2
3
4
5
6
7
|
func NewB() *BImpl {
return new(BImpl)
}
func (b *BImpl) SetA(a service.A) {
b.a = a
}
|
这样就完成了整个的设计,需要调用的时候可以这样调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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循环依赖的问题