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

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

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

Go
PIPIONE 2022-10-24 10:18:30
我正在编写一段依赖于某些实现的代码。我想将实现与我的代码分离,并使实现尽可能独立。我想通过使用接口而不是具体类型来实现这种方法,如下所示:package mypackagetype 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 interfacetype 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 mainimport "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 的接口隐式实现的概念是不是与第三个通用包的想法相矛盾?有没有办法实现这样的事情,而不将两个包绑定在一起?
查看完整描述

2 回答

?
噜噜哒

TA贡献1784条经验 获得超7个赞

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

我认为确实如此。Go 作者引入了一个隐式接口实现来消除包之间不必要的依赖关系。这适用于简单的接口,例如io.Reader,但你不能在任何地方应用它。

语言创建者之一Rob Pike 表示,接口的非声明式满足并不是 Go 中接口背后理念的基本部分。这是一个不错的功能,但并非该语言的所有元素都是实用的或每次都可以使用。

对于复杂的接口,您需要导入定义接口的包。例如,如果您要实现与sql标准库中的包一起使用的 SQL 驱动程序,则必须导入该sql/driver包。

我建议不要在项目开始时引入接口。通常,它会导致您需要解决人工问题,例如每次更新对域模型的理解时重写界面。从第一次尝试中很难得出一个好的抽象,而且在我看来,在许多情况下,这是不必要的。

我需要查询产品的外部来源。我不在乎外部源如何存储数据(数据库、文件、网络)。我只需要一个“产品”类型。所以要么我定义一个 Product 类型,强制外部实现导入并使用它,要么 Go 方式 - 定义一个 Product 接口,让实现隐式实现这个接口。这显然不起作用

我在这里看到两个松散相关的目标:

  1. 定义一个接口来交换产品源的实现。

  2. 实现产品源的包不应导入定义接口的包。

根据我的经验,我建议仅在您至少有一个产品源服务的有效实现时才执行第 1 点。

第2点并不总是可以实现的,这很好;请参阅上面标准 Go 库中的示例。

PS请考虑不创建Product界面。PorductSource虽然最终提出接口确实有意义,Product但很可能只是一组数据;struct是表示此类信息的完美方式。请参阅这个非常相关的代码示例这篇文章以获得灵感。


查看完整回答
反对 回复 2022-10-24
?
人到中年有点甜

TA贡献1895条经验 获得超7个赞

您的方法的问题是您希望有人实现一个引用您的类型(MyType)的接口。如果没有引用您的类型的实现,这显然无法完成。这是阻止上述代码工作的唯一原因。


如果你摆脱MyType:


type TypeGetter interface {

    GetType() interface {

        Title() string

        Price() int

    }

}

和实施:


func (g CustomTypeGetter) GetType() interface {

    Title() string

    Price() int

} {

    return CustomType{"Hello", 42}

}

然后这段代码将起作用:


func main() {

    tg := external.CustomTypeGetter{}

    mypackage.MyHandler(tg)

}

是的,这需要重复,但这只是因为您不希望未知/未来的实现引用您的类型(不依赖于它)。


在这种情况下,您可以更改MyHandler()为采用类型值MyType(摆脱“工厂”):


func MyHandler(t MyType) {

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

}

MyType并且可以传递任何实现的值。在包中添加一个“工厂” external:


func NewCustomType(title string, price int) CustomType {

    return CustomType{

        title: title,

        price: price,

    }

}

并像这样使用它:


func main() {

    t := external.NewCustomType("title", 1)

    mypackage.MyHandler(t)

}

如果你真的需要工厂模式,那么是的,创建一个可以容纳的第三个包MyType是要走的路。然后你的应用程序和实现都可以引用这个第三个包。


查看完整回答
反对 回复 2022-10-24
  • 2 回答
  • 0 关注
  • 74 浏览
慕课专栏
更多

添加回答

举报

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