首页 > 解决方案 > 当有接口及其在不同包中的实现时如何管理循环依赖

问题描述

我的项目结构如下所示:

代码结构:

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 . 因为:

  1. hypervisorpcakge 引用virtualbox.VirtualBox类型
  2. virtualbox包引用hypervisor.MountPath类型

我知道如果我将结构移动MounthPath到另一个包会解决问题,但我认为这不是正确的解决方案设计方式。

有什么建议吗?

标签: gocyclic-dependency

解决方案


在大多数情况下,按照 Dave Cheney 的建议来定义调用者的接口将避免循环依赖。但这只会解决平面数据模型。在您的情况下,您有嵌套实体,即 HyperVisor 具有返回 MounthPath 的功能。我们可以通过两种方式对此进行建模

  1. 在单独的包中定义 MouthPath(如您建议的那样)。此外,从长远来看,在 virtualbox 包中定义接口将有助于为 Hypervisor 提供替代实现。

  2. 让 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
    // ....
} 

推荐阅读