【问题标题】:Where should interfaces be defined when using the factory pattern?使用工厂模式时应该在哪里定义接口?
【发布时间】:2020-02-20 12:41:26
【问题描述】:

我正在使用工厂对象FooFactory 创建具有一些私有数据成员的Foo 类型的实例。我使用工厂,以便创建 Foo 实例的对象 Bar 不需要提供(甚至知道)这些私有数据成员:我首先使用必要的私有内容配置工厂,然后提供 @987654325 @这个已配置的工厂。

我希望Bar 通过接口使用这些Foo 对象,以便我可以使用gomock 模拟它们并测试Bar 是否正确使用它们。根据我在 go 中读到的有关接口的内容,最佳实践是定义使用它们的接口,而不是定义底层类型的位置,因此我在与 Bar 相同的包中有一个 Fooer 接口对象,而Bar 在需要Foo(或更高版本的MockFoo)的任何地方都使用此Fooer 接口。

出于同样的原因,我还希望Bar 通过接口使用FooFactory,因此我可以模拟它并断言它会在我期望的时候创建Foo 对象。同样,我在Bars 包FooBuilder 中定义了一个接口,底层FooFactory 隐式实现了该接口。

现在问题是 FooFactory 返回具体的 Foo 对象,因为它们都在同一个 Foo 包中,不应该知道 Bar 的本地定义接口.但是,我希望FooBuilder 构建Fooer 类型的对象,而不是Foo 类型的对象,因为它不需要了解底层类型。 Go 不允许这样做,因为返回类型不同,并且它表示 FooFactory 没有实现 FooBuilder

这里是没有上述包结构的最小复制:

type Foo struct{}

func (f *Foo) FooMethod() {}

type FooFactory struct{}

func (ff *FooFactory) Build() *Foo {
    return &Foo{}
}

type Fooer interface {
    FooMethod()
}

type FooBuilder interface {
    Build() Fooer
}

func main() {
    f := &Foo{}
    ff := &FooFactory{}

    var fooer Fooer
    var fooBuilder FooBuilder

    fooer = f
    fooBuilder = ff     // << ERROR
}

去抱怨:

cannot use ff (type *FooFactory) as type FooBuilder in assignment:
    *FooFactory does not implement FooBuilder (wrong type for Build method)
        have Build() *Foo
        want Build() Fooer

我的问题本质上是,我这样做吗?在拥有专业 C++ 和 Java 的背景之后,我试图接受 go 的隐式接口满足的概念,但这感觉很奇怪。这并不容易做到这一点让我觉得我做错了什么,但是我在网上读到的关于 go 接口的所有内容都说要定义接近使用它们的接口,这就是我真正想要做的.

【问题讨论】:

  • 在我看来,在开始设计时就考虑了工厂和模拟对象,这似乎使事情变得过于复杂。不要那样做。这在 Java/C++/C# 中可能很好(或者至少是公认的最佳实践)。例如。如果 Foo 和 Bar 位于同一个包中,那么 Bar 了解 Foo 的内部结构可能不会有任何问题。不要整天隐藏信息。甚至 C++ 也认识朋友。不要嘲笑。写存根或伪造。 Mocking 在 Go 中不是惯用的。您的测试位于同一个包中,甚至可以检查内部结构。
  • 如果您坚持使用 Factory 和 Builder:您必须让您的 Factory 返回一个 Fooer。非常简单,这就是 Go 的类型系统的工作原理。这里没有解决方法。考虑使用更多函数或函数闭包而不是对象和方法。 Go 对象通常由函数 NewFoo 构建,而不是通过 fooFactory.Build。如果此构建过程无法从第一原则完成,请尝试为 NewFoo 使用函数闭包。

标签: go interface factory


【解决方案1】:

概念上

你的方法是正确的。如果Foo 的设置不复杂,我会放弃工厂。

FooFooer 可能在也可能不在不同的包中。我会先将它们放在同一个包中,直到它不再有意义。

我通常设置这些情况的方式是:

package foo

func NewFoo() *Foo {
    return &Foo{}
}

type Foo struct{}

func (f *Foo) FooMethod() {}

type Fooer interface {
    FooMethod()
}

还有一个消费者:

package bar

func FooUser(f Fooer) {
    //Do something
}

并将其与:

package main

import (
    "foo"
    "bar"
)

func main() {
    f := foo.NewFoo()
    bar.FooUser(f)
}

这样,您可以将foo 包作为一个单元进行测试,并使用任何实现Fooer 的结构来测试bar 包。

你会发现很多核心包也是这样构建的。

编译器错误

您缺少的一点是 FooFactory 实际上并没有实现 FooBuilder 接口。

FooBuilder 接口声明了一个返回Fooer 的方法,但FooFactory 的方法签名返回一个指向Foo 的指针。

func (ff *FooFactory) Build() *Foo

所以你声明了方法Build,但是你返回了错误的类型,所以它没有被识别为一个实现。

要修复错误,请将Build 的实现签名更改为:

func (ff *FooFactory) Build() Fooer

【讨论】:

  • 我知道 go 抱怨的原因;我更多地问“当我认为我遵循最佳实践时,为什么我最终会陷入这种情况”。诚然,代码示例并没有说明这一点,但问题本身确实说明了这一点:Foo 在一个包中,而Fooer 在另一个包中,Foo 旨在由接口使用(因此可模拟)。
  • 我已经澄清了我的答案
  • 不过,从导出的函数或方法返回非导出类型被认为是不好的做法。
  • @jub0bs 这很有意义。你能引用参考吗?
  • exported func Foo returns unexported type *foo.foo which can be annoying to use?
猜你喜欢
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多