圆形导入结构



我有一个带有几个模块的项目。由于以下情况:

,我在循环导入方面有问题

详细信息

模块游戏包含具有当前游戏状态的结构。另一个模块(修饰符)正在执行一些特定游戏的内容和计算,因此可以修改游戏状态。因此,修饰符将需要结构游戏,但不需要游戏中的任何方法。从游戏中调用修饰符,在这里我们有循环导入。

问题:

  • 游戏启动修饰符

  • 修饰符需要游戏结构

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

带有第三软件包选项:

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

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

要在单独的软件包AB之间拆分某些东西,您通常会尝试隔离可以访问A.Type的API,该API可以表示为接口。然后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…
}

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

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

最新更新