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

在 Golang 中,消费者如何为接受接口的函数定义接口?

在 Golang 中,消费者如何为接受接口的函数定义接口?

Go
翻阅古今 2023-02-21 16:49:07
如果我正确理解 Go 实践,调用者(也称为消费者)应该从他们的依赖项(也称为生产者)中定义他们想要使用的接口。但是,如果生产者有一个接受自定义类型的函数,那么最好让它接受一个接口,对吧?这样消费者就可以传递一些符合生产者接口的值,而不知道确切的类型。因为生产者函数的输入值使生产者成为该输入值的“消费者”。好吧,很公平。问题是,消费者如何定义一个接口,其中包含一个函数,其参数是生产者定义的接口?试图使问题更清楚假设我有一个名为chefstruct 的包Chef。它有一个方法Cut(fruit) error,fruit是在我的包中定义的一个接口chef。现在假设我在调用代码中,我导入了 package chef。我想给它一个水果来切,但在我的例子中,我实现了一个名为Apple. 自然地,我会尝试为自己构建这个界面:type myRequirements interface {   Cut(Apple) error}因为我有接口的具体实现fruit叫Apple,所以我想说明我的接口只适用于苹果。但是,如果我尝试针对我的界面使用Chef{},Go 将抛出编译错误,因为我的界面想要Cut(Apple)和Chef{}想要的Cut(Fruit)。尽管 Apple 实现了fruit.避免这种情况的唯一方法似乎是制作chef.Fruit一个公共界面,并在我自己的界面中使用它。type myRequirements interface {   Cut(chef.Fruit) error}但这完全破坏了我在接口下插入不同实现(而不是chef)的能力,因为现在我与chef.所以 Chef 有一个内部接口fruit,但调用者只知道 Apple。我如何在调用者的界面中指示应该输入什么Cut而不引用chef?回答评论“你为什么需要 myRequirements?”我很惊讶这不是 Go 社区中更一致的概念。我需要 myRequirements 接口的原因是因为我是 chef 包的消费者。此外Cut,厨师可能还有100多种方法。但我只使用Cut. 我想向其他开发人员表明,在我的情况下,我只使用Cut. 我还想让测试只模拟Cut我的代码工作。此外,我需要能够插入不同的实现Cut(来自不同的厨师)。这是我帖子开头提到的 golang 最佳实践。
查看完整描述

3 回答

?
胡子哥哥

TA贡献1825条经验 获得超6个赞

我会说这归结为你能控制什么。在您的示例中,您似乎描述了两个单独的包。有多种方法可以处理此问题:


接受一个函数


您可以修改ApiFunction以接受处理您想要的情况的功能:


type consumerDeps interface {

    ApiFunction(func() string) string

}

这将允许您将您想要的确切功能注入消费者。然而,这里的缺点是这会很快变得混乱,并且会混淆定义函数的意图,并在实现接口时导致意想不到的后果。


接受接口{}


您可以修改ApiFunction以接受interface{}由实现接口的人处理的对象:


type consumerDeps interface {

    ApiFunction(interface{}) string

}


type producer struct{}


type apiFunctionInput interface {

    hello() string

}


func (producer) ApiFunction(i interface{}) string {

    return i.(apiFunctionInput).hello()

}

这稍微好一点,但现在您依赖于生产者端正确解释数据,如果它没有执行此操作所需的所有上下文,那么如果它转换为,您可能会出现意外行为或恐慌错误的类型。


接受第三方接口


您还可以创建一个第三方接口,在这里称之为适配器,它将定义生产者端和消费者端都可以同意的功能:


type Adapter interface {

    hello() string

}


type consumerDeps interface {

    ApiFunction(Adapter) string

}

现在,您有一个数据合同,可用于由消费者发送和由生产者接收。这可能像定义一个单独的包一样简单,也可能像整个存储库一样复杂。


重新设计


最后,你可以重新设计你的代码库,这样生产者和消费者就不会像这样耦合在一起。虽然我不知道您的具体用例,但您遇到这个特定问题这一事实意味着您的代码耦合得太紧,可能应该重新设计。消费者端和生产者端包之间可能有一个元素拆分,可以提取到第三个包。


查看完整回答
反对 回复 2023-02-21
?
qq_遁去的一_1

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

我不太清楚你为什么要介绍这个myRequirements界面。如果Chef需要一个FruittoCut并且你想定义一个特定的水果Apple- 你需要做的就是定义一个Apple实现接口的结构Fruit。


type Chef struct {

}


type fruit interface {

    Cut() error

}


func (c Chef) Cut(fruit fruit) error {

    return fruit.Cut()

}


然后,您需要做的就是根据您的要求定义Apple实现接口的对象:Fruit


package kitchen


import chef "goplayground/interfaces/fruits/chef"


type Apple struct {

}


func (a Apple) Cut() error {

    // lets cut

    return nil

}


type myRequirements interface {

    Cut(Apple) error

}


type myChef struct {

    chef chef.Chef

}


func (m myChef) Cut(apple Apple) error {

    // since Apple implements the chef`s fruit interface this is possible

    return m.chef.Cut(apple)

}


func cook() {

    remy := myChef{}

    apple := Apple{}

    _ = remy.Cut(apple)

}


查看完整回答
反对 回复 2023-02-21
?
慕娘9325324

TA贡献1783条经验 获得超4个赞

正确使用 duck-typing 有一些细微差别,这就是 Go 类型系统在接口方面的作用。在使用它们的地方定义接口通常是一个好习惯,但io.Reader接口是在标准库中定义的。因此,该建议的适用性有限。


在你的例子中,包chef有两个接口,Chef和Fruit. 这两个接口紧密耦合,因为Chef有一个方法使用Fruit. 使用当前的 Go 类型系统,Chef如果不从该包中导出就无法使用Fruit。所以:


type myRequirements interface {

  Cut(chef.Fruit) error

}

是您可以使用包中的实现的chef.Chef唯一方法。Apple


但是你想要做的是:


type myRequirements interface {

  Cut(Apple) error

}

并且您希望能够传达这是 的子集Chef,也就是说, 的语义Cut与 的语义相同Chef。好吧,语义是不同的。否则是不安全的。


说,你实施Apple为:


type Apple struct {}


func (a Apple) SomeFunc()

func (a Apple) FruitFunc()

而是chef.Fruit:


type Fruit interface {

   FruitFunc()

}

显然,Appleimplements chef.Fruit,因此您可以传递到需要Applea 的任何地方。chef.Fruit但是你不能传递chef.Fruit给myRequirements.Cut(Apple)函数。因为 in myRequirements.Cutyou 也暗示你可以使用Apple.SomeFunc, which is not defined in chef.Fruit.


所以,如果你真的想定义一个像 的接口myRequirements,那么你必须使用chef.Fruit. 如果你定义的是using Apple,那么myRequirements.Cut方法就不同于chef.Cut。


查看完整回答
反对 回复 2023-02-21
  • 3 回答
  • 0 关注
  • 94 浏览
慕课专栏
更多

添加回答

举报

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