【问题标题】:Circular import with structs [duplicate]带有结构的循环导入[重复]
【发布时间】:2015-07-13 19:14:28
【问题描述】:

我在 Go 中有一个包含多个模块的项目。由于以下情况,我遇到了循环导入问题:

详情

模块 Game 包含具有当前 Game 状态的结构。另一个模块(修改器)正在做一些游戏特定的东西和计算,因此修改游戏状态。因此,Modifier 将需要结构体 Game,但不需要 Game 中的任何方法。修改器是从游戏中调用的,这里我们有循环导入。

问题:

  • 游戏启动修改器

  • 修改器需要游戏结构

在我看来这是一个常见的情况,所以我想知道我应该如何以最好的方式解决它。我的解决方案是创建第三个模块“结构”,它只包含整个应用程序的所有结构。这是一个好的解决方案吗?

【问题讨论】:

  • 听起来这两个东西需要放在同一个包中,因为它们是如此相互依赖。
  • 可能:1.在Modifier包中,定义一个IGame接口,描述从Game 2中修改器需要什么。当Game启动Modifier时,将Game作为IGame传递给Modifier。

标签: go circular-dependency


【解决方案1】:

使用第三个包选项:

yourgame/
  state/
    state.go
  modifier/
    modifier.go
  main.go

main.go 将两个组件粘合在一起:

import "yourgame/state"
import "yourgame/modifier"

type Game struct {
    state    state.State
    modifier modifier.Modifier
}

func main() {
    // something like: 
    var game Game
    game.modifier.Modify(game.state)
}

不过,这种方法可能耦合得太紧了。与其操纵一个本质上是全局的状态对象,我会尝试将数据分割成你需要的修饰符。

抽象的推理很难,所以这里有一个具体的例子来说明我的意思。在你的游戏中:

type Object struct {
    ID, X, Y int
    // more data here
}
type Game struct {
    Objects map[int]*Object
}

在您的“修改器”中,假设我们有一个可以移动对象的 AI 模块。如果他只关心单个对象的位置,您可以创建一个界面:

// in yourgame/modifier
type Object interface {
    GetCoordinates() (int, int)
    SetCoordinates(int, int)
}
type Modifier struct {}
func (m *Modifier) Update(obj Object) { }

然后我们只需将这些方法添加到我们的原始对象中:

type (obj *Object) GetCoordinates() (int, int) {
    return obj.X, obj.Y
}
type (obj *Object) SetCoordinates(x, y int) {
    obj.X, obj.Y = x, y
}

现在您可以将对象传递给您的修饰符,而无需循环依赖。

现在,如果您的“修改器”界面最终看起来与您的游戏对象几乎完全相同,那么第三个结构包可能是合理的,因此您不必总是重复自己。例如,考虑net/url

【讨论】:

  • 所以,这意味着我必须将interface 分离到其他文件并导入它?
  • Go 中的接口不必是显式的。仅仅实现方法就足以实现接口。
【解决方案2】:

通常,如果包B 具有直接读取/修改A.Type 的代码,那么该代码应该在包A 中。 如果需要直接访问,至少应该是其中的一部分。

要在单独的包AB 之间拆分某些内容,您通常会尝试隔离一个API 以访问可以表示为接口的A.Type。然后B 会定义和使用这个接口,A.Type 会实现它(隐式地,不需要包含 B 的定义)。

然后某些东西(可能是A,可能是一个单独的包)将通过适当地传递A.Type*A.Type 值来使用B

或者根据您的设计,可以颠倒关系,B.OtherType 隐式实现了由A 定义和使用的接口。 或者AB只能通过接口相互使用;这一切都取决于细节。

例如也许是这样的:

package Game // "A"

type State struct {
        data int // etc
}

func (s State) IsValid() bool          { return true }
func (s *State) ChangeY(arg int) error { return nil }
// …etc…

和:

package Modifier // "B"

type GameState interface {
        IsValid() bool
        ChangeY(int) error
}

type M struct {
        s GameState
        //…
}

func New(s GameState) *M {
        return &M{s: s}
}

func (m M) DoSomething() {
        if s.IsValid() {
                // …
        }
        s.ChangeY(42)
        // …etc…
}

【讨论】:

    【解决方案3】:

    我会在同一个包中定义类型(在这种情况下为游戏)及其所有方法。根据语言spec,您甚至无法定义从另一个包导入的类型的方法,

    //you should first do 
    type MyPackageType ImportedType
    //and only then
    func (foo MyPackageType) Modify() {
    ...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-01
      相关资源
      最近更新 更多