go 包循环引用 (import cycle not allowed)

前言

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

go 为什么不允许循环引用

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

  1. 加快编译速度
  2. 规范框架设计,使项目结构更加清晰明了

定位循环引用

如上图所示,想要定位到循环引用的具体文件,具体的代码位置是比较困难的,特别是当项目文件特别多的时候。这边有一个项目引用可视化的工具,可以查看包引用的关系godepgraph。可以生成如下的引用图:
在这里插入图片描述
感兴趣的可以自行查看更多的使用方式。

使用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 更新

最近在做项目的时候,发现上述包循环引用的方法并不适用,现使用另一种更为直观的方式,A 注册一个方法到 B, 如下例子:

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())
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值