Golang中循环导入问题的解决方法

Golang中循环导入问题的解决方法 大家好,

我正在学习Go语言,发现与某些编程语言不同,Go不允许循环导入。我找到了这篇文章为什么Go中禁止循环导入 | Jauhar’s Blog,解释了其中的原因。据我所知,不允许循环导入是因为init函数可能会变成一个无限递归函数。 我的问题是,上面的文章准确吗?

4 回复

嗯,循环依赖在任何编程语言中都是不被允许的,尽管在某些语言中,如果发现循环依赖,它们会被透明地移除。这是因为循环依赖会导致无限循环。

更多关于Golang中循环导入问题的解决方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我已经阅读了这篇文章,老实说,我不能确认其准确性是100%。首先,作者声称其他语言允许循环导入。在他给出的列表中,我可以肯定Python 100%不允许这样做。提供的示例也需要更多的澄清。总而言之,循环导入是一种不良实践,如果任何语言以隐式或显式的方式允许它,我都会感到遗憾。这可能导致许多隐藏的错误和误解。

以下是 Rob Pike 解释这一点的引用

Go 语言中禁止导入循环,这迫使程序员更多地思考他们的依赖关系,保持依赖图的清晰和构建的快速。相反,允许循环会导致懒惰、糟糕的依赖管理和缓慢的构建。最终,你会得到一个包含整个依赖图的单一循环块,并迫使它成为一个单一的构建对象。这对构建性能和依赖解析非常不利。而且,要解开这些循环块所需的工作量,远比一开始就保持依赖图是一个合适的 DAG(有向无环图)要大得多。

这是一个前期保持简单性非常值得的领域。

导入循环可能很方便,但其代价可能是灾难性的。应该继续禁止它们。

无需多言。

循环导入在Go中确实被禁止,但原因比文章中提到的更复杂。主要问题在于编译器的依赖解析和包初始化顺序,而不仅仅是init函数的递归问题。

根本原因:

  1. 编译依赖图必须是DAG(有向无环图) - 编译器需要确定包的编译顺序
  2. 包初始化顺序 - Go需要确定init函数的执行顺序
  3. 类型检查 - 循环依赖会导致类型解析的鸡生蛋问题

示例:

// package a/a.go
package a
import "b"
type A struct {
    B *b.B
}

// package b/b.go  
package b
import "a"  // 循环导入错误
type B struct {
    A *a.A
}

解决方案:

  1. 接口解耦(最推荐):
// package a/a.go
package a
type AInterface interface {
    DoSomething()
}

// package b/b.go
package b
import "a"
type B struct {
    Dep a.AInterface
}
  1. 提取公共代码到新包
// package common/types.go
package common
type SharedType struct {
    // 共享类型定义
}

// package a/a.go  
package a
import "common"

// package b/b.go
package b
import "common"
  1. 使用函数/方法注入
// package a/a.go
package a
func NewA(bDep interface{}) *A {
    return &A{dep: bDep}
}

// package b/b.go  
package b
func NewB(aDep interface{}) *B {
    return &B{dep: aDep}
}
  1. 重构包结构 - 重新组织代码,消除循环依赖

文章提到的init函数递归只是循环导入问题的表现之一,根本原因在于Go的编译模型要求清晰的依赖层次结构。

回到顶部