在Go语言中通过接口进行解耦… 接口实现者的切片?

huangapple go评论68阅读模式
英文:

Decoupling via Interfaces in Go... Slice of interface implementors?

问题

好的,以下是翻译的内容:

好的。我知道这是一个常见问题,我认为答案是“放弃吧,它不会按照你的方式工作”,但我只是想确保我没有漏掉什么。

我还在努力理解接口的最佳实践和规则。我有一些代码位于不同的包中,我希望保持解耦,就像这样(不起作用,否则我就不会在这里了):

package A

type Foo struct {}

func (f *Foo) Bars() ([]*Foo, error) {
    foos := make([]*Foo, 0)

    // 一些循环将一堆相关的*Foo追加到foos中
    return foos, nil
}

package B

type Foolike interface {
    Bars() []Foolike
}

func DoSomething(f Foolike) error {
    // blah
}

使用这段代码,编译器会报错:

cannot use f (type *A.Foo) as type Foolike in argument to B.DoSomething:
*A.Foo does not implement Foolike (wrong type for Bars method)
	have Bars() ([]*A.Foo, error)
	want Bars() ([]Foolike, error)

现在,我明白了 []Foolike 并不是一个接口签名本身;它是 Foolike 接口的切片的签名。我也明白编译器将 []A.Foo 和 []Foolike 视为不同的东西,因为...(咕噜咕噜内存分配,严格类型*咕噜咕噜)。

我的问题是:有没有一种正确的方法来实现我最终想要的效果,即让 B.DoSomething() 接受 *A.Foo 而不必导入 A 并在 B.DoSomething() 的函数签名(或者更糟糕的是,在接口定义中)中使用 *A.Foo?我并不想试图欺骗编译器或进行疯狂的运行时技巧。我知道我可以改变 Foo.Bars() 的实现以返回 []Foolike,但那似乎很愚蠢和错误(为什么 A 需要知道 B 的任何信息?这破坏了解耦的整个目的!)。

<strike>我猜另一种选择是将 Bars() 从接口的要求中移除,并依赖其他方法来强制执行该要求。不过,这种方法不太理想(如果 Bars() 是唯一的公开方法怎么办?)。</strike> 编辑:不,那行不通,因为我不能在 DoSomething() 中使用 Bars(),因为它没有在接口中定义。唉。

如果我只是在错误地做事,我会接受并找出其他解决办法,但我希望我只是没有理解它应该如何工作的某个方面。

英文:

OK. I know this is a FAQ, and I think the answer is "give up, it doesn't work that way", but I just want to make sure I'm not missing something.

I am still wrapping my head around best practices and rules for use of interfaces. I have code in different packages that I'd prefer to keep decoupled, something like so (doesn't work, or I wouldn't be here):

package A

type Foo struct {}

func (f *Foo) Bars() ([]*Foo, error) {
    foos := make([]*Foo, 0)

    // some loop which appends a bunch of related *Foo to foos
    return foos, nil
}

package B

type Foolike interface {
    Bars() []Foolike
}

func DoSomething(f Foolike) error {
    // blah
}

With this, the compiler complains:

cannot use f (type *A.Foo) as type Foolike in argument to B.DoSomething:
*A.Foo does not implement Foolike (wrong type for Bars method)
	have Bars() ([]*A.Foo, error)
	want Bars() ([]Foolike, error)

Now, I grok that []Foolike is not an interface signature itself; it's the signature for a slice of Foolike interfaces. I think I also grok that the compiler treats []*A.Foo and []Foolike as different things because ... (mumble memory allocation, strict typing mumble).

My question is: Is there a correct way to do what I ultimately want, which is to let B.DoSomething() accept an *A.Foo without having to import A and use *A.Foo in B.DoSomething()'s function signature (or worse, in the interface definition)? I'm not hung up on trying to trick the compiler or get into crazy runtime tricks. I understand that I could probably change the implementation of Foo.Bars() to return []Foolike, but that seems stupid and wrong (Why should A have to know anything about B? That breaks the whole point of decoupling things!).

<strike>I guess another option is to remove Bars() as a requirement for implementing the interface and rely on other methods to enforce the requirement. That feels less than ideal, though (what if Bars() is the only exported method?).</strike> Edit: No, that won't work because then I can't use Bars() in DoSomething(), because it's not defined in the interface. Sigh.

If I'm just Doing It Wrong™, I'll accept that and figure something else out, but I hope I'm just not getting some aspect of how it's supposed to work.

答案1

得分: 1

正如错误信息所说,你不能将[]FooLike[]*Foo类型互换使用。

对于[]*Foo切片,内存中的支持数组将如下所示:

| value1 | value2 | value3 | ... | valueN |

由于我们知道这些值的类型将是*Foo,它们可以按顺序直接存储。相比之下,[]FooLike切片中的每个元素可能是不同的类型(只要它们符合FooLike)。因此,支持数组将更像是这样的:

| type1 | value1 | type2 | value2 | type3 | value3 | ... | typeN | valueN |

因此,无法简单地在这两种类型之间进行转换:需要创建一个新的切片并复制值。

因此,你的底层类型需要返回一个接口类型的切片才能正常工作。

英文:

As the error message says, you can't treat the []FooLike and []*Foo types interchangeably.

For the a []*Foo slice, the backing array will look something like this in memory:

| value1 | value2 | value3 | ... | valueN |

Since we know the values are going to be of type *Foo, they can be stored sequentially in a straight forward manner. In contrast, each element in a []FooLike slice could be of a different type (provided they conform to FooLike). So the backing array would look more like:

| type1 | value1 | type2 | value2 | type3 | value3 | ... | typeN | valueN |

So it isn't possible to do a simple cast between the types: it would be necessary to create a new slice and copy over the values.

So your underlying type will need to return a slice of the interface type for this to work.

huangapple
  • 本文由 发表于 2014年12月3日 00:35:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/27254381.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定