如何解决Golang中的循环导入问题?

huangapple go评论68阅读模式
英文:

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)

huangapple
  • 本文由 发表于 2023年2月1日 12:42:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/75305738.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定