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

如果我正确理解 Go 实践,调用者(也称为消费者)应该从他们的依赖项(也称为生产者)中定义他们想要使用的接口。

但是,如果生产者有一个接受自定义类型的函数,那么最好让它接受一个接口,对吧?这样消费者就可以传递一些符合生产者接口的值,而不知道确切的类型。因为生产者函数的输入值使生产者成为该输入值的“消费者”。

好吧,很公平。

问题是,消费者如何定义一个接口,其中包含一个函数,其参数是生产者定义的接口?

试图使问题更清楚

假设我有一个名为chefstruct 的包Chef。它有一个方法Cut(fruit) errorfruit是在我的包中定义的一个接口chef

现在假设我在调用代码中,我导入了 package chef。我想给它一个水果来切,但在我的例子中,我实现了一个名为Apple. 自然地,我会尝试为自己构建这个界面:

type myRequirements interface {
  Cut(Apple) error}

因为我有接口的具体实现fruitApple,所以我想说明我的接口只适用于苹果。

但是,如果我尝试针对我的界面使用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 最佳实践。


翻阅古今
浏览 99回答 3
3回答

胡子哥哥

我会说这归结为你能控制什么。在您的示例中,您似乎描述了两个单独的包。有多种方法可以处理此问题:接受一个函数您可以修改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}现在,您有一个数据合同,可用于由消费者发送和由生产者接收。这可能像定义一个单独的包一样简单,也可能像整个存储库一样复杂。重新设计最后,你可以重新设计你的代码库,这样生产者和消费者就不会像这样耦合在一起。虽然我不知道您的具体用例,但您遇到这个特定问题这一事实意味着您的代码耦合得太紧,可能应该重新设计。消费者端和生产者端包之间可能有一个元素拆分,可以提取到第三个包。

qq_遁去的一_1

我不太清楚你为什么要介绍这个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实现接口的对象:Fruitpackage kitchenimport 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)}

慕娘9325324

正确使用 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。
打开App,查看更多内容
随时随地看视频慕课网APP