go - 当有接口及其在不同包中的实现时如何管理循环依赖
问题描述
我的项目结构如下所示:
代码结构:
hypervisor
├── hypervisor.go
├── hyperv
│ └── hyperv.go
└── virtualbox
├── vbox.go
└── vboxprops.go
源代码:
//hypervisor/hypervisor.go
package hypervisor
type Hypervisor interface {
Start(vmName string) error
ListMounts(vmName string) ([]MountPath, error)
//....
}
type MountPath struct {
HostPath string
GuestPath string
}
func detect() (Hypervisor, error) {
return &virtualbox.Virtualbox{}, nil // <<1 HERE
}
// ... other code
并有另一个(嵌套)包:
//hypervisor/virtualbox/vbox.go
package virtualbox
type Virtualbox struct {
}
func (*Virtualbox) Start(vmName string) error {
return vboxManage("startvm", vmName, "--type", "headless").Run()
}
func (*Virtualbox) ListMounts(vmName string) ([]hypervisor.MountPath, error) { // <<2 HERE
// ....
}
// ... other code
正如所见,当然,这样的代码会导致import cycle not allowed
. 因为:
hypervisor
pcakge 引用virtualbox.VirtualBox
类型virtualbox
包引用hypervisor.MountPath
类型
我知道如果我将结构移动MounthPath
到另一个包会解决问题,但我认为这不是正确的解决方案设计方式。
有什么建议吗?
解决方案
在大多数情况下,按照 Dave Cheney 的建议来定义调用者的接口将避免循环依赖。但这只会解决平面数据模型。在您的情况下,您有嵌套实体,即 HyperVisor 具有返回 MounthPath 的功能。我们可以通过两种方式对此进行建模
在单独的包中定义 MouthPath(如您建议的那样)。此外,从长远来看,在 virtualbox 包中定义接口将有助于为 Hypervisor 提供替代实现。
让 virtualbox 将 Hypervisor 和 MounthPath 都定义为接口。一个缺点是管理程序实现包使用 virtualbox.MouthPath 接口来满足如下传递时的接口。
//hypervisor/hypervisor.go
package hypervisor
type Hypervisor struct{
someField []virtualbox.MountPath
}
type MountPath struct { // this can be used as virtualbox.MountPath
hostPath string
guestPath string
}
func (m *MountPath) HostPath() string { return m.hostPath }
func (m *MountPath) GuestPath() string { return m.guestPath }
func detect() (Hypervisor, error) {
return &virtualbox.Virtualbox{}, nil // <<1 HERE
}
并有另一个包(不需要嵌套)
//hypervisor/virtualbox/vbox.go
package virtualbox
type Hypervisor interface {
Start(vmName string) error
ListMounts(vmName string) ([]MountPath, error)
//....
}
type MountPath interface {
HostPath() string
GuestPath() string
}
type Virtualbox struct {}
func (*Virtualbox) Start(vmName string) error {
return vboxManage("startvm", vmName, "--type", "headless").Run()
}
func (*Virtualbox) ListMounts(vmName string) ([]MountPath, error) { // <<2 HERE
// ....
}