在Golang中使用结构进行循环导入

时间:2015-05-04 11:54:28

标签: go circular-dependency

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

详细

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

问题:

  • 游戏启动修改器

  • 修饰符需要游戏结构

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

3 个答案:

答案 0 :(得分:3)

使用第3个包选项:

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

答案 1 :(得分:0)

通常,如果包B具有直接读取/修改A.Type的代码,则该代码应位于包A中。 如果需要直接访问,至少它的部分应该。

要在单独的包AB之间拆分,您通常会尝试隔离用于访问可以表示为接口的A.Type的API。然后B将定义并使用此接口,A.Type将实现它(隐式地,无需包含B的定义)。

然后,某些内容(可能是A,可能是一个单独的包)会通过BA.Type值来使用*A.Type

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

E.g。也许是这样的:

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…
}

答案 2 :(得分:0)

我在同一个包中定义了类型(本例中为Game)及其所有方法。您甚至无法根据语言spec

定义从另一个包导入的类型的方法
//you should first do 
type MyPackageType ImportedType
//and only then
func (foo MyPackageType) Modify() {
...
}