猿问

使用接口作为参数创建另一个包的等效接口

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

所以很自然地,我在包 b 中声明了一个接口,其中包含我想从包 a 调用的函数的签名。问题是这个函数接受一个特定类型的参数,这个参数是在包 a 中声明的接口。因为我不希望包 b 导入包 a,所以我在包 b 中定义了一个接口,其签名与包 a 中存在的签名完全相同。下面的 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() 代码存在于连接这两者的单独包中。


吃鸡游戏
浏览 92回答 2
2回答

慕容3067478

IIUC,你的问题不是关于包,而是归结为一个函数(或方法)是否可以类型转换为另一个函数,该函数接受具有等效但不同接口类型的参数。像这样的东西:package maintype 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规范是这样说的函数类型表示具有相同参数和结果类型的所有函数的集合。类型func (I1)和func (interface{})不采用相同的参数,即使 I1定义为interface{}. 由于类似的原因,您的代码无法编译,因为func (runner RunnerB)它不相同func (runner Runner),因此方法集 *Manager不是接口的超集runnerRegisterer。回到你原来的问题:我正在练习编写惯用的 Go 代码,发现接口应该在使用它们的包中声明,因为它们是隐式的。是的,这个想法很好,但它并不像您认为的那样适用于您的实施。由于您期望有不同的实现, runnerRegisterer并且它们都必须具有使用接口的具有相同签名的方法,因此在公共位置Runner定义是有意义的。Runner此外,如上所示,无论如何,Go 不允许您在方法签名中使用不同的接口。根据我对您要实现的目标的理解,以下是我认为您应该重新安排代码的方式:定义RunnerRegisterer(注意:这是公开的)并Runner在一个包中。在同一个包中实现您的RunnerCoach并使用上述接口。您RunnerCoach使用实现接口的类型,因此它定义了它们。在另一个包中实施您的跑步者。您不在Runner 此处定义接口。在另一个包中实现你的,它使用在 的包中定义的Manager接口 ,因为如果它想用作.RunnerRunnerCoachRunnerRegisterer

呼唤远方

解决方案您有一个类型在两个包 A 和 B 中使用。包 A 导入包 B。你必须避免循环依赖,所以你有三个选择:在包 B 中定义类型,因此它在两个包中都可用。在包 C 中定义类型,让 A 和 B 都导入包 C。更改您的设计,使 A 和 B 中都不使用该类型。无论该类型是接口还是任何其他类型,这些都是您的选择。选择最适合您的设计目标的选项。规则/成语我正在练习编写惯用的 Go 代码,发现接口应该在使用它们的包中声明,因为它们是隐式的。我有了冲动/想法——问题是它不太实用。如果接口只能在定义它们的包中使用,那么它们的用处就会小一些。标准库中充满了违反这条公理的代码。因此,我不认为所提出的规则——适用于所有情况、设计和上下文中的所有界面——是惯用的。例如,查看bytes包。 func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)consumes io.Reader,在另一个包中定义的接口。 func (r *Reader) WriteTo(w io.Writer) (n int64, err error)consumes io.Writer,在另一个包中定义的接口。
随时随地看视频慕课网APP

相关分类

Go
我要回答