外部包如何隐式实现接口?

我正在编写一段依赖于某些实现的代码。

我想将实现与我的代码分离,并使实现尽可能独立。

我想通过使用接口而不是具体类型来实现这种方法,如下所示:


package mypackage


type MyType interface {

    Title() string

    Price() int

}


type TypeGetter interface {

    GetType() MyType

}


func MyHandler(tg TypeGetter) {

    t := tg.GetType()

    fmt.Printf("Title: %s, Price: %d", t.Title(), t.Price())

}

一个实现可能是这样的:


package external


// CustomType implicitly implements the MyType interface

type CustomType struct {

    title string

    price int

}

func (t CustomType) Title() string { return t.title }

func (t CustomType) Price() int { return t.price }



// CustomTypeGetter implicitly implements the TypeGetter interface. Or is it???

type CustomTypeGetter struct {

}

func (g CustomTypeGetter) GetType() CustomType {

    return CustomType{"Hello", 42}

}

然后,代码将执行以下操作:


package main


import "mypackage"

import "external"


func main() {

    tg := external.CustomTypeGetter{}

    mypackage.MyHandler(tg)            // <--- the compiler does not like this

}

我希望这个例子能说明问题:我在“mypackage”和“external”包之间没有耦合,它可以被替换,代替我的模拟进行测试等。


问题:编译器抱怨调用MyHandler有一个实现的对象:

func GetType() CustomType,而不是:

func GetType() MyType


我找到的唯一解决方案是将接口声明(MyType和TypeGetter)移动到第三个包,然后“mypackage”和“external”包都可以使用它。

但我想避免这种情况。


Go 的接口隐式实现的概念是不是与第三个通用包的想法相矛盾?


有没有办法实现这样的事情,而不将两个包绑定在一起?


PIPIONE
浏览 77回答 2
2回答

噜噜哒

Go 的接口隐式实现的概念是不是与第三个通用包的想法相矛盾?我认为确实如此。Go 作者引入了一个隐式接口实现来消除包之间不必要的依赖关系。这适用于简单的接口,例如io.Reader,但你不能在任何地方应用它。语言创建者之一Rob Pike 表示,接口的非声明式满足并不是 Go 中接口背后理念的基本部分。这是一个不错的功能,但并非该语言的所有元素都是实用的或每次都可以使用。对于复杂的接口,您需要导入定义接口的包。例如,如果您要实现与sql标准库中的包一起使用的 SQL 驱动程序,则必须导入该sql/driver包。我建议不要在项目开始时引入接口。通常,它会导致您需要解决人工问题,例如每次更新对域模型的理解时重写界面。从第一次尝试中很难得出一个好的抽象,而且在我看来,在许多情况下,这是不必要的。我需要查询产品的外部来源。我不在乎外部源如何存储数据(数据库、文件、网络)。我只需要一个“产品”类型。所以要么我定义一个 Product 类型,强制外部实现导入并使用它,要么 Go 方式 - 定义一个 Product 接口,让实现隐式实现这个接口。这显然不起作用我在这里看到两个松散相关的目标:定义一个接口来交换产品源的实现。实现产品源的包不应导入定义接口的包。根据我的经验,我建议仅在您至少有一个产品源服务的有效实现时才执行第 1 点。第2点并不总是可以实现的,这很好;请参阅上面标准 Go 库中的示例。PS请考虑不创建Product界面。PorductSource虽然最终提出接口确实有意义,Product但很可能只是一组数据;struct是表示此类信息的完美方式。请参阅这个非常相关的代码示例和这篇文章以获得灵感。

人到中年有点甜

您的方法的问题是您希望有人实现一个引用您的类型(MyType)的接口。如果没有引用您的类型的实现,这显然无法完成。这是阻止上述代码工作的唯一原因。如果你摆脱MyType:type TypeGetter interface {&nbsp; &nbsp; GetType() interface {&nbsp; &nbsp; &nbsp; &nbsp; Title() string&nbsp; &nbsp; &nbsp; &nbsp; Price() int&nbsp; &nbsp; }}和实施:func (g CustomTypeGetter) GetType() interface {&nbsp; &nbsp; Title() string&nbsp; &nbsp; Price() int} {&nbsp; &nbsp; return CustomType{"Hello", 42}}然后这段代码将起作用:func main() {&nbsp; &nbsp; tg := external.CustomTypeGetter{}&nbsp; &nbsp; mypackage.MyHandler(tg)}是的,这需要重复,但这只是因为您不希望未知/未来的实现引用您的类型(不依赖于它)。在这种情况下,您可以更改MyHandler()为采用类型值MyType(摆脱“工厂”):func MyHandler(t MyType) {&nbsp; &nbsp; fmt.Printf("Title: %s, Price: %d", t.Title(), t.Price())}MyType并且可以传递任何实现的值。在包中添加一个“工厂” external:func NewCustomType(title string, price int) CustomType {&nbsp; &nbsp; return CustomType{&nbsp; &nbsp; &nbsp; &nbsp; title: title,&nbsp; &nbsp; &nbsp; &nbsp; price: price,&nbsp; &nbsp; }}并像这样使用它:func main() {&nbsp; &nbsp; t := external.NewCustomType("title", 1)&nbsp; &nbsp; mypackage.MyHandler(t)}如果你真的需要工厂模式,那么是的,创建一个可以容纳的第三个包MyType是要走的路。然后你的应用程序和实现都可以引用这个第三个包。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go