英文:
Circular import with structs
问题
我有一个使用Go语言编写的项目,其中包含几个模块。我在处理下面的场景时遇到了循环导入的问题:
详细信息
一个名为Game的模块包含一个包含当前游戏状态的结构体。另一个模块(Modifier)执行一些与游戏相关的操作和计算,因此修改了游戏状态。因此,Modifier需要Game结构体,但不需要Game的任何方法。Modifier从Game中调用,这就导致了循环导入。
问题:#
-
Game初始化Modifier
-
Modifier需要Game结构体
在我看来,这是一个常见的场景,所以我想知道如何以最佳方式解决它。我的解决方案是创建一个名为"Structs"的第三个模块,它只包含整个应用程序的所有结构体。这个解决方案好吗?
英文:
I have a project with several modules in Go. 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?
答案1
得分: 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() {
// 类似于:
var game Game
game.modifier.Modify(game.state)
}
然而,这种方法可能过于紧密耦合。与其操作一个本质上是全局状态对象,不如将数据切分为仅需要修改器所需的部分。
在抽象层面进行推理很困难,所以这里给出一个具体示例来说明我的意思。在你的游戏中:
type Object struct {
ID, X, Y int
// 更多数据在这里
}
type Game struct {
Objects map[int]*Object
}
在你的“modifier”中,假设我们有一个移动对象的 AI 模块。如果他只关心单个对象的位置,你可以创建一个接口:
// 在 yourgame/modifier 中
type Object interface {
GetCoordinates() (int, int)
SetCoordinates(int, int)
}
type Modifier struct {}
func (m *Modifier) Update(obj Object) { }
然后,我们只需将这些方法添加到我们原始的 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
}
现在,您可以将对象传递给修改器,而无需循环依赖。
如果你的“modifier”接口最终看起来几乎与你的游戏对象完全相同,那么一个第三个包的结构可能是合理的,这样你就不必总是重复自己。以 net/url
为例考虑。
英文:
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
.
答案2
得分: 0
通常情况下,如果包B
中的代码直接读取/修改A.Type
,那么该代码应该在包A
中。至少其中需要直接访问的部分应该在包A
中。
要在分开的包A
和B
之间拆分某些内容,你通常会尝试隔离一个用于访问A.Type
的API,该API可以表示为一个接口。然后,B
将定义并使用此接口,而A.Type
将实现它(隐式地,无需包含B对其的定义)。
然后,某个东西(可能是A
,也可能是一个单独的包)将通过传递适当的A.Type
或*A.Type
值来使用B
。
或者,根据你的设计,关系可以反过来,B.OtherType
隐式地实现了A
定义并使用的接口。
或者,A
和B
都可以只通过接口相互使用;这完全取决于具体情况。
例如,可能是这样的:
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…
}
英文:
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…
}
答案3
得分: 0
我会为你翻译以下内容:
我会在同一个包中定义类型(在这种情况下是Game)以及它的所有方法。根据语言规范,你甚至不能为从另一个包导入的类型定义方法。
//你应该先这样做
type MyPackageType ImportedType
//然后才能这样做
func (foo MyPackageType) Modify() {
...
}
英文:
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() {
...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论