英文:
How to solve the import cycle problem in Golang?
问题
我遇到了一个循环导入的问题。我读了一些博客,但是不太理解。这是我写的示例代码。
file1.go
package dir1
type Filo interface {
File2(string) string
}
func File1(message string) string {
var f Filo
importFile2 := f.File2("file 2")
return "欢迎来到" + message + ",来自文件2的消息:" + importFile2
}
file2.go
package dir2
import "github.com/ibilalkayy/app/dir1"
func File2(message string) string {
importFile1 := dir1.File1("file 1")
return "欢迎来到" + message + ",来自文件1的消息:" + importFile1
}
main.go
package main
import (
"fmt"
"github.com/ibilalkayy/app/dir1"
"github.com/ibilalkayy/app/dir2"
)
func main() {
fmt.Println("Hello world")
first := dir1.File1("file 1")
second := dir2.File2("file 2")
fmt.Println(first)
fmt.Println(second)
}
英文:
I have encountered an import cycle problem. I read some blogs but didn't understand them. This is the sample code that I have written.
file1.go
package dir1
type Filo interface {
File2(string) string
}
func File1(message string) string {
var f Filo
importFile2 := f.File2("file 2")
return "Welcome to " + message + " and message from file 2: " + importFile2
}
file2.go
package dir2
import "github.com/ibilalkayy/app/dir1"
func File2(message string) string {
importFile1 := dir1.File1("file 1")
return "Welcome to " + message + " and message from file 1: " + importFile1
}
main.go
package main
import (
"fmt"
"github.com/ibilalkayy/app/dir1"
"github.com/ibilalkayy/app/dir2"
)
func main() {
fmt.Println("Hello world")
first := dir1.File1("file 1")
second := dir2.File2("file 2")
fmt.Println(first)
fmt.Println(second)
}
答案1
得分: 4
对于导入循环的需求通常意味着设计存在问题。
避免导入循环有两个常见解决方案:
- 如果两个包之间紧密相关且彼此依赖,最好将它们(或核心功能)合并为一个单独的包。这样可以避免导入循环。
- 或者,其中一个包应该使用接口,这样它就不会依赖于另一个包。这样可以创建包层次的自然顺序。
例如,下面这个人为的例子由于导入循环而无法编译:
package main
import "example.com/base"
import "example.com/other"
func main() {
f := &other.Foo{}
_ = base.Process(f)
}
package base
import "example.com/other"
func Process(f *other.Foo) {
for {
if err := f.Frob(); err != nil {
return err
}
}
}
package other
import "example.com/base"
type Foo int
func (f *foo) Frob(name string) error {
if *f == 0 {
return errors.New("completed")
}
*f--
return base.Process(f)
}
可以通过将base
改为使用接口来修复这个问题:
package base
type Frobber interface {
Frob(string) error
}
func Process(f Frobber) error {
for {
if err := f.Frob("some file"); err != nil {
return err
}
}
}
这样做的原因是*other.Foo
可以传递给base.Process
,而无需base
包了解other
包的任何信息。
另一个选项是使用函数变量:
// 给定:
package other
import "example.com/base"
type Foo struct {}
func (f *Foo) Frob(name string) error
package base
import "example.com/other"
func Process(f *Foo) error
// 使用以下方式:
package base
func Process(fn func(string) error) error
// 这样可以使用:
package main
import "example.com/other"
func main() {
f := &other.Foo{} // 注意:这需要是真实的并初始化过的。
Process(f.Frob)
}
问题中的示例除了导入循环之外还存在根本性的错误。由于相互递归,它将耗尽堆栈空间。忽略这一点,你可以使用无类型的func
使其编译通过。例如:
func File1(fn func(string) string) string {
return "Welcome to message from file 2: " + fn("some file")
}
// 调用方式:
func something(name string) string {
return "foo/" + name
}
dir1.File1(something)
英文:
The desire for an import cycle is usually an indication there is something wrong with the design.
There are 2 general solutions to avoid import cycles:
- If 2 packages are so closely tied and they depend on each other, it is probably better to merge them (or core functionality) into a single package. This avoids import cycles.
- Alternatively, one of the packages should use an interface so it does not depend on the other package. This creates a natural ordering of package layers.
For example, the following contrived example won't compile due to the import cycle:
package main
import "example.com/base"
import "example.com/other"
func main() {
f := &other.Foo{}
_ = base.Process(f)
}
package base
import "example.com/other"
func Process(f *other.Foo) {
for {
if err := f.Frob(); err != nil {
return err
}
}
}
package other
import "example.com/base"
type Foo int
func (f *foo) Frob(name string) error {
if *f == 0 {
return errors.New("completed")
}
*f--
return base.Process(f)
}
This can be fixed by changing base
to use an interface:
package base
type Frobber interface {
Frob(string) error
}
func Process(f Frobber) error {
for {
if err := f.Frob("some file"); err != nil {
return err
}
}
}
This works since *other.Foo
can to passed to base.Process
without the base
package needing to know anything about the other
package.
Another option is to use function variables:
// Given:
package other
import "example.com/base"
type Foo struct {}
func (f *Foo) Frob(name string) error
package base
import "example.com/other"
func Process(f *Foo) error
// Use this instead:
package base
func Process(fn func(string) error) error
// Which enables using:
package main
import "example.com/other"
func main() {
f := &other.Foo{} // NOTE: This needs to be real an initialised.
Process(f.Frob)
}
The example in the question is fundamentally broken beyond the import cycle. It will run out of stack space due to mutual recursion. Ignoring that, you can get it to compile with an untyped func
. Eg:
func File1(fn func(string) string) string {
return "Welcome to message from file 2: " + fn("some file")
}
// Call with:
func something(name string) string {
return "foo/" + name
}
dir1.File1(something)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论