修复 Go 中的导入周期
Fixing import cycle in Go
所以我有这个导入周期,我正试图解决这个问题。我有以下模式:
view/
- view.go
action/
- action.go
- register.go
总体思路是动作在视图上执行,并由视图执行:
// view.go
type View struct {
Name string
}
// action.go
func ChangeName(v *view.View) {
v.Name = "new name"
}
// register.go
const Register = map[string]func(v *view.View) {
"ChangeName": ChangeName,
}
然后在 view.go 我们调用这个:
func (v *View) doThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
但这会造成循环,因为View依赖于Action包,反之亦然。我该如何解决
这个周期?有没有不同的方法来解决这个问题?
导入周期是设计错误的结果。在两个方向上相互依赖的结构必须在同一个包中,否则将发生导入循环。顺便说一下,Go 并不是唯一有这种限制的编程语言。它也存在于 C++ 和 Python 中,例如
导入周期表明设计存在根本性错误。从广义上讲,您正在查看以下内容之一:
- 你在混淆问题。也许
view
根本不应该访问 action.Register
,或者也许 action
不应该负责更改视图的名称(或两者)。这似乎是最有可能的。
- 你依赖的是一个实体,而你应该依赖一个接口并注入一个实体。例如,视图不是直接访问
action.Register
,而是可以在 view
中定义的接口类型上调用方法,并在构造时注入到 View
对象中。
- 您需要一个或多个额外的、单独的包来保存
view
和 action
包使用的逻辑,但它们都不调用。
一般来说,您想要构建一个应用程序以便拥有三种基本类型的包:
- 完全独立的包,不引用其他第一方包(当然可以引用标准库或其他第三方包)。
- 仅内部依赖性为上述类型 1 的逻辑包,即完全独立的包。这些包不应相互依赖或依赖于以下类型 3 的包。
- "Wiring" 包,主要与逻辑包交互,处理实例化、初始化、配置和依赖注入。这些可以依赖于除其他类型 3 包之外的任何其他包。您应该需要非常非常少的这种类型的包 - 通常只需要一个,
main
,但对于更复杂的应用程序,偶尔需要两个或三个。
基本上,您可以通过引入接口并注入接口而不是结构来打破依赖关系。
以您的示例为例:
// view.go
package view
import "import_cycles/action"
type View struct {
Name string
}
func (v *View) ModifyName(name string) {
v.Name = name
}
func (v *View) DoThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
// action.go
package action
func ChangeName(v NameChanger) {
v.ModifyName("new name")
}
// register.go
package action
type NameChanger interface {
ModifyName(name string)
}
var Register = map[string]func(v NameChanger){
"ChangeName": ChangeName,
}
请注意 NameChanger
接口被引入。这里要点以下几点:
- 这个接口注入函数 ChangeName 而不是传递 struct
- struct View 正在实现这个接口
因此,包“action”不再需要导入包“view”,因为界面位于同一个包“action”中
在main.go中我们可以测试结果:
v := &view.View{
Name: "some name",
}
v.DoThings()
fmt.Println(v)
// &{new name}
所以我有这个导入周期,我正试图解决这个问题。我有以下模式:
view/
- view.go
action/
- action.go
- register.go
总体思路是动作在视图上执行,并由视图执行:
// view.go
type View struct {
Name string
}
// action.go
func ChangeName(v *view.View) {
v.Name = "new name"
}
// register.go
const Register = map[string]func(v *view.View) {
"ChangeName": ChangeName,
}
然后在 view.go 我们调用这个:
func (v *View) doThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
但这会造成循环,因为View依赖于Action包,反之亦然。我该如何解决 这个周期?有没有不同的方法来解决这个问题?
导入周期是设计错误的结果。在两个方向上相互依赖的结构必须在同一个包中,否则将发生导入循环。顺便说一下,Go 并不是唯一有这种限制的编程语言。它也存在于 C++ 和 Python 中,例如
导入周期表明设计存在根本性错误。从广义上讲,您正在查看以下内容之一:
- 你在混淆问题。也许
view
根本不应该访问action.Register
,或者也许action
不应该负责更改视图的名称(或两者)。这似乎是最有可能的。 - 你依赖的是一个实体,而你应该依赖一个接口并注入一个实体。例如,视图不是直接访问
action.Register
,而是可以在view
中定义的接口类型上调用方法,并在构造时注入到View
对象中。 - 您需要一个或多个额外的、单独的包来保存
view
和action
包使用的逻辑,但它们都不调用。
一般来说,您想要构建一个应用程序以便拥有三种基本类型的包:
- 完全独立的包,不引用其他第一方包(当然可以引用标准库或其他第三方包)。
- 仅内部依赖性为上述类型 1 的逻辑包,即完全独立的包。这些包不应相互依赖或依赖于以下类型 3 的包。
- "Wiring" 包,主要与逻辑包交互,处理实例化、初始化、配置和依赖注入。这些可以依赖于除其他类型 3 包之外的任何其他包。您应该需要非常非常少的这种类型的包 - 通常只需要一个,
main
,但对于更复杂的应用程序,偶尔需要两个或三个。
基本上,您可以通过引入接口并注入接口而不是结构来打破依赖关系。
以您的示例为例:
// view.go
package view
import "import_cycles/action"
type View struct {
Name string
}
func (v *View) ModifyName(name string) {
v.Name = name
}
func (v *View) DoThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
// action.go
package action
func ChangeName(v NameChanger) {
v.ModifyName("new name")
}
// register.go
package action
type NameChanger interface {
ModifyName(name string)
}
var Register = map[string]func(v NameChanger){
"ChangeName": ChangeName,
}
请注意 NameChanger
接口被引入。这里要点以下几点:
- 这个接口注入函数 ChangeName 而不是传递 struct
- struct View 正在实现这个接口
因此,包“action”不再需要导入包“view”,因为界面位于同一个包“action”中
在main.go中我们可以测试结果:
v := &view.View{
Name: "some name",
}
v.DoThings()
fmt.Println(v)
// &{new name}