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

时间:2018-09-15 23:18:49

标签: go interface

我正在练习编写惯用的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()代码存在于连接这两者的单独软件包中。

2 个答案:

答案 0 :(得分: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,则作为参数。

答案 1 :(得分:0)

解决方案

您已经在两个软件包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,这是另一个包中定义的接口。