前言

在项目中,我们或多或少都会遇到go的包循环引用的问题,类似这样
import cycle
这就让人很头疼,为什么在其他语言中都没见过这种错误呢?这我们就得来谈谈go的设计理念了。

go 为什么不允许循环引用

曾经有人提议Go语言作者Rob Pike,想要在Go以后的版本去掉循环引入;Rob Pike坚决不同意。Rob Pike觉得假如你两个包之间存在循环引入的问题,那一定是你在设计之初就没考虑好模块的划分。
这样设计的好处:

  1. 加快编译速度
  2. 规范框架设计,使项目结构更加清晰明了
定位循环引用 使用interface来解决循环引用

例子:包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())
}