在项目中,我们或多或少都会遇到go的包循环引用的问题,类似这样
这就让人很头疼,为什么在其他语言中都没见过这种错误呢?这我们就得来谈谈go的设计理念了。
曾经有人提议Go语言作者Rob Pike,想要在Go以后的版本去掉循环引入;Rob Pike坚决不同意。Rob Pike觉得假如你两个包之间存在循环引入的问题,那一定是你在设计之初就没考虑好模块的划分。
这样设计的好处:
- 加快编译速度
- 规范框架设计,使项目结构更加清晰明了
例子:包p1和包p2循环引用,包p1实现包p2的接口。
目录结构
包p1
package p1
import (
"fmt"
"../p2"
)
type SayHello struct{}
func (*SayHello) SayHello() {
fmt.Println("i'm p1, hello!!!")
}
func (h *SayHello) SayHelloFromP2() {
pup := p2.New(h)
pup.SayP1Hello()
}
包p2
package p2
import (
"fmt"
)
type ISayHello interface {
SayHello()
}
type p2UseP1 struct {
realP1 ISayHello
}
// 如果不能直接使用p1,就把p1传过来,我给个interface接收下,我在内部使用
func New(h ISayHello) *p2UseP1 {
return &p2UseP1{
h,
}
}
// p1 say hello
func (p *p2UseP1) SayP1Hello() {
p.realP1.SayHello()
}
// p2 say hello
func (p *p2UseP1) SayP2Hello() {
fmt.Println("i'm p2, hello!!!")
}
main
package main
import "./p1"
// 测试:包的循环引用
func main() {
var h p1.SayHello
h.SayHello() // i'm p1, hello!!!
h.SayHelloFromP2() // i'm p1, hello!!!
}
总结
go 出现包循环引用的问题,首当其冲的就是程序结构没设计好,最好的方式就是在项目框架构建的时候,将各个模块设计好,避免出现该问题。当项目内容较少的时候,可以通过重构来重新梳理项目架构,但随着项目的规模扩大,重构的成本增加,不可能每次出现类似的问题都去重构代码,这样可能会得不偿失。尝试分层的设计,高层依赖于低层,低层不依赖于高层,在开发的过程中始终将这些理念记在心中。当两个包有紧密耦合关系时,最好将它们放到一个包内。实在不行的情况下,我们可以使用interface来解决包依赖的问题。
=================================================================
2021.06.26 更新AB
A <-> B 循环引用,A 调用了 B 的 SetSB() 函数,B 调用了 A 的 SetSA() 函数
目录结构
package A
package A
import "../B"
func init() {
B.RegisterSetSAEvent(SetSA)
}
func UseB() {
B.SetSB("A use B")
}
var a string
func SetSA(value string) {
a = value
}
func GetSA() string {
return a
}
package B
package B
var b string
func SetSB(value string) {
b = value
}
func GetSB() string {
return b
}
var SetSAEventHandler func(value string)
func RegisterSetSAEvent(f func(value string)) {
SetSAEventHandler = f
}
func UseA() {
if SetSAEventHandler != nil {
SetSAEventHandler("B USE A")
}
}
package main
import (
"./A"
"./B"
"fmt"
)
func main() {
A.UseB()
B.UseA()
fmt.Println(A.GetSA())
fmt.Println(B.GetSB())
}