简体   繁体   English

在不同的包中有接口及其实现时如何管理循环依赖

[英]How to manage cyclic dependencies when have interface and its implementation in different packages

I have my project structure looks like this:我的项目结构如下所示:

Structure of code:代码结构:

hypervisor
├── hypervisor.go
├── hyperv
│   └── hyperv.go
└── virtualbox
    ├── vbox.go
    └── vboxprops.go

Source code:源代码:

//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

And have another (nested) package :并有另一个(嵌套)包:

//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

And as seen, of course, such code leads to import cycle not allowed .正如所见,当然,此类代码会导致import cycle not allowed because of:因为:

  1. hypervisor pcakge referencing virtualbox.VirtualBox type hypervisor pcakge 引用virtualbox.VirtualBox类型
  2. virtualbox package referencing hypervisor.MountPath type引用hypervisor.MountPath类型的virtualbox

I know if I move the struct MounthPath to another package would solve the issue, but I don't think is the correct solution design-wise.我知道如果我将 struct MounthPath移动到另一个包会解决这个问题,但我认为这不是正确的解决方案设计。

Any suggestion?有什么建议吗?

One of easiest way I would do is to separate entities into entities package for example (in this case: the Hypervisor and Virtualbox struct are entities or whatever you want to call it).我会做的最简单的方法之一是将实体分离到entities包中(在这种情况下: HypervisorVirtualbox结构是实体或任何你想称呼它的东西)。
This is most common design I think, so every struct that inner packages use will not cause cyclic deps.这是我认为最常见的设计,因此内部包使用的每个结构都不会导致循环 deps。
Example of usage: all time package structs are on top package level.用法示例:所有time包结构都在顶级包级别。 time.Time{} , time.Duration{} , etc. time.Duration does not sit on time/duration package. time.Time{}time.Duration{}time.Duration不属于time/duration包。

Following suggestions from Dave Cheney to define interfaces by the caller will avoid cycle dependencies in most cases.在大多数情况下,遵循 Dave Cheney 的建议由调用者定义接口将避免循环依赖。 But this will only solve flat data models.但这只能解决平面数据模型。 In your case, you have nested entities ie., HyperVisor has fucntion which returns MounthPath.在您的情况下,您有嵌套实体,即 HyperVisor 具有返回 MounthPath 的功能。 We can model this in two ways我们可以通过两种方式对此进行建模

  1. Define MouthPath in separate package (like you suggested).在单独的包中定义 MouthPath(如您所建议的)。 In addition, defining the interface in the virtualbox package will help in long term to provide alternative implementation for Hypervisor.此外,在 virtualbox 包中定义接口将有助于为 Hypervisor 提供替代实现。

  2. Let virtualbox define both Hypervisor and MounthPath as interface.让 virtualbox 将 Hypervisor 和 MounthPath 定义为接口。 One disadvantage is that the hypervisor implementing package use virtualbox.MouthPath interface to satisfy the interface when passed like below.一个缺点是管理程序实现包使用 virtualbox.MouthPath 接口来满足如下传递时的接口。

//hypervisor/hypervisor.go //管理程序/管理程序.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
}

And have another package (Need not be nested)并有另一个包(不需要嵌套)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM