所以我有一个正在尝试解决的导入周期。我有以下模式:
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包,反之亦然。我该如何解决 这个周期?有其他方法可以解决这个问题吗?
导入周期表明设计存在根本性错误。一般来说,您正在查看以下内容之一:
view
根本不应该访问 action.Register
,或者 action
不应该负责更改视图名称(或两者)。这似乎是最有可能的。action.Register
,而是可以调用 view
中定义的接口类型上的方法,并在构造时注入到 View
对象中。view
和 action
包使用的逻辑,但它们都不会调用。一般来说,您希望构建一个应用程序,以便拥有三种基本类型的包:
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
界面。这里需要注意以下几点:
因此,包“action”不再需要导入包“view”,因为界面放置在同一个包“action”中
在main.go中我们可以测试结果:
v := &view.View{
Name: "some name",
}
v.DoThings()
fmt.Println(v)
// &{new name}
就我自己而言,我在单元测试中创建了一个简单的
import cycle
。 我的普通应用程序很好。
为了回答您有关修复的问题,首先我隔离了导致
import cycle
的函数。 就我而言,import cycle
仅在运行测试时发生。
然后我检查了导入周期的类型。 这有助于可视化错误。 我发现B包测试依赖于A包。
我将测试移至包 A 中,不再使用
import cycle
[和更干净的测试]。
导入周期是设计错误的结果。双向相互依赖的结构必须位于同一个包中,否则将发生导入循环。顺便说一句,Go 并不是唯一有此限制的编程语言。例如,它也存在于 C++ 和 Python 中。
通过依赖注入解决循环依赖的一种方法,这样你就可以打破一个包对另一个包的直接依赖。
尝试从 main.go 文件注入依赖项