为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
吃鸡游戏 2023-04-04 15:11:51
我正在练习编写惯用的 Go 代码,发现接口应该在使用它们的包中声明,因为它们是隐式的。但是我遇到了这种情况,在第二个包(包 b)中,我想要一个函数来调用包 a 中的结构的接收函数,而不紧密耦合它。所以很自然地,我在包 b 中声明了一个接口,其中包含我想从包 a 调用的函数的签名。问题是这个函数接受一个特定类型的参数,这个参数是在包 a 中声明的接口。因为我不希望包 b 导入包 a,所以我在包 b 中定义了一个接口,其签名与包 a 中存在的签名完全相同。下面的 playground 链接显示了示例代码。操场package mainimport (    "fmt"    "log")func main() {    manager := &Manager{}    coach := NewRunnerCoach(manager)    fmt.Println("Done")}// package atype Runner interface {    Run()}type Manager struct {}func (o *Manager) RegisterRunner(runner Runner) {    log.Print("RegisterRunner")}func (o *Manager) Start() {    log.Print("Start")}// package btype 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 acceptingtype runnerRegisterer interface {    RegisterRunner(runner RunnerB)}// declaring a new interface with the same signature because we dont want to import package a// and import Runner interfacetype RunnerB interface {    Run()}此代码无法编译。所以这里的问题是,我是在错误地使用接口,还是应该在单独的包中定义具体类型,或者最后,是否有更好的代码模式来解决我要解决的问题?编辑:澄清一下,包 a 和 b 不会相互导入。main() 代码存在于连接这两者的单独包中。
查看完整描述

2 回答

?
慕容3067478

TA贡献1773条经验 获得超3个赞

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

像这样的东西:

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规范是这样说的

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

类型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接口 ,因为如果它想用作.RunnerRunnerCoachRunnerRegisterer


查看完整回答
反对 回复 2023-04-04
?
呼唤远方

TA贡献1856条经验 获得超11个赞

解决方案

您有一个类型在两个包 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)consumes io.Reader,在另一个包中定义的接口。 func (r *Reader) WriteTo(w io.Writer) (n int64, err error)consumes io.Writer,在另一个包中定义的接口。


查看完整回答
反对 回复 2023-04-04
  • 2 回答
  • 0 关注
  • 76 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信