【问题标题】:Create equivalent interface of another package with an interface as argument使用接口作为参数创建另一个包的等效接口
【发布时间】:2019-02-20 07:27:59
【问题描述】:

我正在练习编写惯用的 Go 代码,并发现接口应该在使用它们的包中声明,因为它们是隐式的。但是我遇到了这种情况,在第二个包(包 b)中,我想要一个函数来调用包 a 中结构的接收器函数,而不是紧密耦合它。

所以很自然,我在包 b 中声明了一个接口,并带有我想从包 a 调用的函数的签名。问题是这个函数接受某个类型的参数,它是包 a 中声明的接口。由于我不希望包 b 导入包 a,因此我在包 b 中定义了一个接口,其签名与包 a 中存在的签名完全相同。下面的 Playground 链接显示了示例代码。

Playground

package main

import (
    "fmt"
    "log"
)

func main() {
    manager := &Manager{}
    coach := NewRunnerCoach(manager)
    fmt.Println("Done")
}

// package a

type Runner interface {
    Run()
}

type Manager struct {
}

func (o *Manager) RegisterRunner(runner Runner) {
    log.Print("RegisterRunner")
}

func (o *Manager) Start() {
    log.Print("Start")
}

// package b

type RunnerCoach struct {
    runner *FastRunner
}

func NewRunnerCoach(registerer runnerRegisterer) *RunnerCoach {
    runnerCoach := &RunnerCoach{&FastRunner{}}
    registerer.RegisterRunner(runnerCoach.runner)
    return runnerCoach
}

type FastRunner struct {
}

func (r *FastRunner) Run() {
    log.Print("FastRunner Run")
}

// define ther registerer interface coach is accepting
type runnerRegisterer interface {
    RegisterRunner(runner RunnerB)
}

// declaring a new interface with the same signature because we dont want to import package a
// and import Runner interface
type RunnerB interface {
    Run()
}

此代码无法编译。所以这里的问题是,我是错误地使用了接口,还是应该在单独的包中定义具体类型,或者最后,对于我要解决的问题,是否有更好的代码模式?

编辑:为了澄清,包 a 和 b 不会相互导入。 main() 代码存在于连接这两者的单独包中。

【问题讨论】:

    标签: go interface


    【解决方案1】:

    解决方案

    您有一个用于两个包 A 和 B 的类型。包 A 导入包 B。

    你要避免循环依赖,所以你有三种选择:

    1. 在包 B 中定义类型,使其在两个包中都可用 包。
    2. 在包 C 中定义类型,并让 A 和 B 都导入包 C。
    3. 更改您的设计,使 A 和 B 均不使用该类型。

    无论该类型是接口还是任何其他类型,这些都是您的选择。

    选择最适合您的设计目标的选项。

    规则/成语

    我正在练习编写惯用的 Go 代码并发现 接口应该在使用它们的包中声明 因为它们是隐含的。

    我得到了冲动/想法 - 问题是它不是很实用。如果接口只能在定义它们的包中使用,那么它们的用处就会降低。标准库中充满了违反此公理的代码。因此,我不认为所呈现的规则——适用于所有情况、设计和上下文中的所有接口——是惯用的。

    例如,查看bytes 包。 func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) 使用 io.Reader,这是在另一个包中定义的接口。 func (r *Reader) WriteTo(w io.Writer) (n int64, err error) 使用 io.Writer,这是在另一个包中定义的接口。

    【讨论】:

    • 它不能编译不是因为循环依赖。在上面的示例中,包 A 和 B 不会相互导入。目的是 main()(称为包 M)代码通过接口将 2 个包连接在一起。
    • 问题是 RunnerB (pkg b) 接口未被检测为等效于 Runner (pkg a) 接口,尽管它们定义了相同的函数签名,因此类型检查失败。
    • 是的,我知道。我尝试了您的游乐场代码并对其进行了调整,以便在两个包中使用相同的接口类型。如果我不理解问题,我就不会按照我的方式回答。
    • 编辑后的答案更加清晰,并提供了有关为什么它不是一致公理的相关参考。谢谢!
    【解决方案2】:

    IIUC,您的问题不是关于包,而是归结为函数(或方法) 可以被类型转换为另一个接受等效参数的函数,但是 不同的接口类型。

    类似这样的:(Go Playground)

    package main
    
    type I1 interface{}
    
    func f1(x I1) {}
    
    func main() {
        f := (func(interface{}))(f1)
        f(nil)
    }
    

    编译错误:./g.go:8:26: cannot convert f1 (type func(I1)) to type func(interface {})

    答案似乎是否定的,因为 Go 不认为 func (I1) 是 相当于func (interface{})Go spec 这么说

    函数类型表示具有相同参数和结果类型的所有函数的集合。

    func (I1)func (interface{}) 类型不采用相同的参数,即使 I1 定义为 interface{}。您的代码无法编译为类似 原因是因为func (runner RunnerB)func (runner Runner) 不同,因此*Manager 的方法集不是 接口runnerRegisterer

    回答你原来的问题:

    我正在练习编写惯用的 Go 代码并发现接口 应该在使用它们的包中声明,因为它们是 隐含的。

    是的,这个想法很好,但它不适用于您的实现方式 认为是的。由于您期望有不同的实现 runnerRegisterer 并且它们都必须具有具有相同签名的方法 使用Runner 接口,定义Runner 是有意义的 地方。此外,如上所示,Go 不允许您使用不同的界面 无论如何都在方法签名中。

    根据我对您想要实现的目标的理解,我的想法如下 你应该重新安排你的代码:

    1. 定义RunnerRegisterer(注意:这是公开的)和Runner合二为一 包。
    2. 在同一个包中实现你的RunnerCoach并使用上面的 接口。您的 RunnerCoach 使用实现接口的类型, 所以它定义了它们。
    3. 在另一个包中实现您的跑步者。你没有定义Runner 界面在这里。
    4. 在另一个使用接口的包中实现你的Manager Runner 定义在 RunnerCoach 的包中,因为它必须采用该类型 如果它想用作RunnerRegisterer,则作为参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多