如果我正确理解 Go 实践,调用者(又名消费者)应该从其依赖项(又名生产者)定义他们想要使用的接口。
\n但是,如果生产者有一个接受自定义类型的函数,那么最好让它接受一个接口,对吗?这样消费者就可以传递一些符合生产者接口的值,而无需知道确切的类型。因为生产者函数的输入值使生产者成为该输入值的“消费者”。
\n好吧,很公平。
\n问题是,消费者如何定义一个接口,其中包含一个函数,其参数是生产者中定义的接口?
\n假设我有一个名为chefstruct 的包Chef。它有一个方法Cut(fruit) error,并且fruit是我的包中定义的接口chef。
现在假设我在调用代码中,并且导入了 package chef。我想给它一个水果来切,但就我而言,我实现了一个名为 的特定水果Apple。当然,我会尝试为自己构建这个界面:
type myRequirements interface {\n Cut(Apple) error\n}\nRun Code Online (Sandbox Code Playgroud)\nfruit因为我有接口的具体实现称为Apple,所以我想表明我的接口仅适用于苹果。
但是,如果我尝试Chef{}针对我的接口使用,Go 将抛出编译错误,因为我的接口想要Cut(Apple)并且Chef{}想要Cut(Fruit)。尽管苹果实现了这一点fruit。
看来,避免这种情况的唯一方法是创建chef.Fruit一个公共接口,并在我自己的接口中使用它。
type myRequirements interface {\n Cut(chef.Fruit) error\n}\nRun Code Online (Sandbox Code Playgroud)\n但这完全破坏了我chef在接口下插入不同实现(而不是 )的能力,因为现在我与chef.
所以Chef有一个内部接口fruit,但调用者只知道Apple。如何在调用者的界面中指示应输入哪些输入Cut而不引用chef?
令我惊讶的是,这在 Go 社区中还没有得到更一致的概念。
\n我需要 myRequirements 接口的原因是因为我\xe2\x80\x99m 是厨师包的消费者。除此之外Cut,厨师可能还有100多种方法。但我只用Cut. 我想向其他开发人员表明,在我的情况下,我\xe2\x80\x99m 仅使用Cut. 我还希望允许测试仅模拟Cut我的代码才能工作。此外,我需要能够插入不同的实现Cut(来自不同的厨师)。这是我在文章开头提到的 golang 最佳实践。
一些引用作为证据:
\nGolang Wiki 说:“Go 接口通常属于使用接口类型值的包,而不是实现这些值的包。”
\nDave Cheney 的博客解释道:“接口声明调用者需要的行为,而不是类型将提供的行为。让调用者定义一个描述他们期望的行为的接口。该接口属于他们,消费者,而不是你。”
\nJason Moiron 的推文指出了一个常见的误解:“人们把它搞反了:#golang 接口是为使用它们的函数而存在的,而不是为了描述实现它们的类型”
\n到目前为止,我得到的最好建议是将接口移动到第三个包中,独立于调用者和生产者。例如,制作一个包,在其中kitchen定义接口,并在厨师和调用者中使用它。Fruit有点像大家都用的time.Time。也许这是最好的建议。也就是说,我仍然希望从那些试图在实际工作中解决这个问题的人那里获得权威的观点。
我想说这取决于你能控制什么。在您的示例中,您似乎描述了两个单独的包。有多种方法可以处理这个问题:
接受函数
您可以修改ApiFunction以接受处理您想要的情况的函数:
type consumerDeps interface {
ApiFunction(func() string) string
}
Run Code Online (Sandbox Code Playgroud)
这将使您能够向消费者注入您想要的确切功能。然而,这样做的缺点是,这很快就会变得混乱,并且可能会混淆定义函数的意图,并在实现接口时导致意想不到的后果。
接受接口{}
您可以修改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()
}
Run Code Online (Sandbox Code Playgroud)
这好一点,但现在您依赖于生产者端来正确解释数据,如果它没有执行此操作所需的所有上下文,那么如果它转换为,您可能会出现意外的行为或恐慌类型错误。
接受第三方接口
你还可以创建一个第三方接口,这里称之为Adapter,它将定义生产者端和消费者端都可以同意的功能:
type Adapter interface {
hello() string
}
type consumerDeps interface {
ApiFunction(Adapter) string
}
Run Code Online (Sandbox Code Playgroud)
现在,您有了一个数据合约,消费者可以使用它来发送数据,生产者可以使用它来接收数据。这可能像定义一个单独的包一样简单,也可能像整个存储库一样复杂。
重新设计
最后,您可以重新设计代码库,这样生产者和消费者就不会像这样耦合在一起。尽管我不知道您的具体用例,但您遇到这个特定问题的事实意味着您的代码耦合得太紧,并且可能应该重新设计。消费者端和生产者端包之间可能存在一个元素分割,可以将其提取到第三个包中。
| 归档时间: |
|
| 查看次数: |
2616 次 |
| 最近记录: |