Golang中带有结构的循环导入[重复]

This question already has an answer here:

I have a project with several modules in Golang. I am having problem with circular imports because of the scenario below:

Details

A module Game contains a struct with the current Game state. Another module (Modifier) is doing some game specific stuff and calculations and therefore modifies the game state. Because of this, Modifier will need the struct Game, but not any methods from Game. Modifier is called from Game and here we have the circular import.

Problem:

  • Game initiates Modifier

  • Modifier needs Game struct

It seems to me that this is a common scenario, so I wonder how I should solve it in the best way. My solution would be to create a third module "Structs" which just contains all the structs for the whole application. Is this a good solution?

</div>

Normally if package B has code that directly reads/modifies A.Type then that code should be in package A. At least the parts of it if that need direct access should.

To split something between separate packages A and B you'd normal try to isolate an API for access to A.Type that can be expressed as an interface. Then B would define and use this interface and A.Type would implement it (implicitly, without needing to include B's definition of it).

Then something (possibly A, possibily a separate package) would use B by passing an A.Type or *A.Type value as appropriate.

Or depending on your design the relationship could be reversed, with B.OtherType implicitly implementing an interface defined and used by A. Or both A and B could use each other only through interfaces; it all depends on the details.

E.g. perhaps something like:

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…

and:

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

I'd define type(Game in this case) and all its methods in same one package. You can't even define methods on type imported from another package according to language spec,

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

With the 3rd package option:

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

main.go would glue the two components together:

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

This approach is probably too tightly coupled though. Rather than manipulating an essentially global state object, I would try to slice up the data into just what you need for the modifier.

Reasoning in the abstract is hard, so here's a concrete example of what I mean. In your game:

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

In your "modifier", let's suppose we had an AI module that moves an object. If all he cares about is the position of a single object you can create an interface:

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

Then we just have to add those methods to our original 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
}

And now you can pass objects to your modifier without needing a cyclic dependency.

Now if it turns out your "modifier" interface ends up looking almost exactly the same as your game object, then a 3rd package of structs is probably reasonable so you aren't always repeating yourself. For an example consider net/url.