修复 Go 中的导入周期

问题描述 投票:0回答:5

所以我有一个正在尝试解决的导入周期。我有以下模式:

view/
- view.go
action/
- action.go
- register.go

总体思路是动作在视图上执行,并由视图执行:

// view.go
type View struct {
    Name string
}

// action.go
func ChangeName(v *view.View) {
    v.Name = "new name"
}

// register.go
const Register = map[string]func(v *view.View) {
    "ChangeName": ChangeName,
}

然后在 view.go 中我们调用这个:

func (v *View) doThings() {
    if action, exists := action.Register["ChangeName"]; exists {
        action(v)
    }
}

但这会造成循环,因为View依赖于Action包,反之亦然。我该如何解决 这个周期?有其他方法可以解决这个问题吗?

go dependencies circular-dependency
5个回答
32
投票

导入周期表明设计存在根本性错误。一般来说,您正在查看以下内容之一:

  • 你的担忧很复杂。也许
    view
    根本不应该访问
    action.Register
    ,或者
    action
    不应该负责更改视图名称(或两者)。这似乎是最有可能的。
  • 您依赖于一个具体,而您应该依赖一个接口并注入一个具体。例如,视图不是直接访问
    action.Register
    ,而是可以调用
    view
    中定义的接口类型上的方法,并在构造时注入到
    View
    对象中。
  • 您需要一个或多个额外的独立包来保存
    view
    action
    包使用的逻辑,但它们都不会调用。

一般来说,您希望构建一个应用程序,以便拥有三种基本类型的包:

  1. 完全独立的包,不引用其他第一方包(它们当然可以引用标准库或其他第三方包)。
  2. 仅内部依赖关系属于上述类型 1 的逻辑包,即完全独立的包。这些包不应相互依赖或依赖于下面类型 3 的包。
  3. “接线”包,主要与逻辑包交互,并处理实例化、初始化、配置和依赖项注入。这些可以依赖于除其他类型 3 包之外的任何其他包。您应该需要非常非常少的这种类型的包 - 通常只需要一个,
    main
    ,但对于更复杂的应用程序,偶尔需要两个或三个。

3
投票

基本上,您可以通过引入接口并注入接口而不是结构来打破依赖关系。

按照你的例子,它看起来像:

// view.go
package view

import "import_cycles/action"

type View struct {
    Name string
}

func (v *View) ModifyName(name string) {
    v.Name = name
}

func (v *View) DoThings() {
    if action, exists := action.Register["ChangeName"]; exists {
        action(v)
    }
}

// action.go
package action

func ChangeName(v NameChanger) {
    v.ModifyName("new name")
}

// register.go
package action

type NameChanger interface {
    ModifyName(name string)
}

var Register = map[string]func(v NameChanger){
    "ChangeName": ChangeName,
}

请注意引入

NameChanger
界面。这里需要注意以下几点:

  • 此接口注入到函数 ChangeName 中,而不是传递结构体
  • struct View正在实现这个接口

因此,包“action”不再需要导入包“view”,因为界面放置在同一个包“action”中

在main.go中我们可以测试结果:

v := &view.View{
    Name: "some name",
}
v.DoThings()
fmt.Println(v)

// &{new name}

2
投票

就我自己而言,我在单元测试中创建了一个简单的

import cycle
。 我的普通应用程序很好。

import cycle simple

为了回答您有关修复的问题,首先我隔离了导致

import cycle
的函数。 就我而言,
import cycle
仅在运行测试时发生。

然后我检查了导入周期的类型。 这有助于可视化错误。 我发现B包测试依赖于A包。

我将测试移至包 A 中,不再使用

import cycle
[和更干净的测试]。


0
投票

导入周期是设计错误的结果。双向相互依赖的结构必须位于同一个包中,否则将发生导入循环。顺便说一句,Go 并不是唯一有此限制的编程语言。例如,它也存在于 C++ 和 Python 中。


0
投票

通过依赖注入解决循环依赖的一种方法,这样你就可以打破一个包对另一个包的直接依赖。

尝试从 main.go 文件注入依赖项

© www.soinside.com 2019 - 2024. All rights reserved.