我正在练习编写惯用的Go代码,发现接口应该在使用它们的包中声明,因为它们是隐式的。但是我遇到了这种情况,在第二个程序包(程序包b)中,我希望函数调用程序包a中的结构的接收器函数,而又不能紧密耦合它。
因此,很自然地,我在包b中声明了一个接口,该接口带有要从包a调用的函数的签名。问题在于此函数接受某个特定类型的参数,该参数是在程序包a中声明的接口。因为我不希望包b导入包a,所以我在包b中定义了一个接口,该接口具有与包a中存在的接口完全相同的签名。下面的操场链接显示了示例代码。
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()代码存在于连接这两者的单独软件包中。
答案 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不允许您使用其他界面
仍然在方法签名中。
基于我对您要实现的目标的理解,这就是我的想法 您应该重新排列代码:
RunnerRegisterer
(请注意:这是公开的)定义为Runner
包。RunnerCoach
放在同一软件包中并使用上面的
接口。您的RunnerCoach
使用实现接口的类型,
因此它定义了它们。Runner
界面。Manager
插入另一个使用界面的软件包中
Runner
的包中定义了RunnerCoach
,因为它必须采用该类型
如果想用作RunnerRegisterer
,则作为参数。答案 1 :(得分:0)
您已经在两个软件包A和B中使用了一种类型。软件包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
,这是另一个包中定义的接口。