接口具有相同的方法,但被视为不同。

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

Interfaces have same methods but are considered as different

问题

为什么具有相同方法的两个命名接口被视为不同的接口 - 如何避免这种情况?

假设我们有一个喜欢吃产品的人(Eater)。他不在乎吃什么产品,他只想知道从哪里可以获取新产品。换句话说,他想要产品服务,但不在乎产品服务会生产什么产品。在具体的实现中,我们将尝试用苹果来喂他,所以我们将提供给他苹果服务(appleService)。

以下是错误信息:

./main.go:9: cannot use appleService (type *service.AppleService) as type eater.ProductServiceI in function argument:
*service.AppleService does not implement eater.ProductServiceI (wrong type for New method)
have New() service.ProductI
want New() eater.ProductI

接口service.AppleIeater.AppleI具有相同的方法Eat(),但是Go语言将它们视为不同的接口。为什么会这样,如何避免这种情况?根据鸭子类型,这应该是可以工作的,因为ProductServiceI实际上只要求提供的结构体具有Eat()方法 - 它不应该关心接口的名称(service.ProductIeater.ProductI)。

以下是完整的代码:

==> ./main.go <==

package main

import &quot;./apple/service&quot;
import &quot;./eater&quot;

func main() {
    appleService := &amp;service.AppleService{}
    // func eater.New(productService ProductServiceI)
    appleEater := eater.New(appleService) 
    appleEater.EatUntilHappy()
}

==> ./eater/eater.go <==

package eater

type ProductServiceI interface {
    New() ProductI
}

type ProductI interface {
    Eat()
}

type Eater struct {
    productService ProductServiceI
}

func New(productService ProductServiceI) *Eater {
    return &amp;Eater{
        productService: productService,
    }
}

func (a *Eater) EatUntilHappy() {
    for i:=0; i &lt; 5; i++ {
        product := a.productService.New()
        product.Eat()
    }
}

==> ./apple/service/service.go <==

package service

import &quot;./apple&quot;

type ProductI interface {
    Eat()
}

type AppleService struct {
}

func (a *AppleService) New() ProductI {
    return &amp;apple.Apple{}
}

==> ./apple/service/apple/apple.go <==

package apple

import &quot;fmt&quot;

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println(&quot;mniam, mniam&quot;)
}

我认为接口的名称或导入路径不重要,只要声明相同就可以。

英文:

Why two named interfaces with same methods are treated as different ones - and how can you avoid that?

So let's say we have a guy who likes to eat products (Eater). He doesn't care what products he eats, he only wants to be pointed out from where he can fetch new products. In other words he wants product service, but doesn't care what products product service will produce. In concrete implementation we will try to feed him with apples, so we will provide him with appleService.

Here is the result:

./main.go:9: cannot use appleService (type *service.AppleService) as type eater.ProductServiceI in function argument:
        *service.AppleService does not implement eater.ProductServiceI (wrong type for New method)
                have New() service.ProductI
                want New() eater.ProductI

Interfaces service.AppleI and eater.AppleI have same method Eat() and nothing else but golang considers them as different ones. Why and how to avoid that? According to duck typing this should work because what actually ProductServiceI requires is that provided struct has Eat() method - it shouldn't care about what name has interface (service.ProductI vs eater.ProductI).

Below is full code:

==> ./main.go <==

package main

import &quot;./apple/service&quot;
import &quot;./eater&quot;

func main() {
    appleService := &amp;service.AppleService{}
    // func eater.New(productService ProductServiceI)
    appleEater := eater.New(appleService) 
    appleEater.EatUntilHappy()
}

==> ./eater/eater.go <==

package eater

type ProductServiceI interface {
    New() ProductI
}

type ProductI interface {
    Eat()
}

type Eater struct {
    productService ProductServiceI
}

func New(productService ProductServiceI) *Eater {
    return &amp;Eater{
        productService: productService,
    }
}

func (a *Eater) EatUntilHappy() {
    for i:=0; i &lt; 5; i++ {
        product := a.productService.New()
        product.Eat()
    }
}

==> ./apple/service/service.go <==

package service

import &quot;./apple&quot;

type ProductI interface {
    Eat()
}

type AppleService struct {
}

func (a *AppleService) New() ProductI {
    return &amp;apple.Apple{}
}

==> ./apple/service/apple/apple.go <==

package apple

import &quot;fmt&quot;

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println(&quot;mniam, mniam&quot;)
}

I thought it doesn't matter what name or what import path has interface as long as declaration is the same.

答案1

得分: 4

我认为你这里有几个问题。首先,你在main和service中都定义了AppleI接口,这是重复定义。其次,你似乎试图使用Python或Java(我猜的)风格的包。值得阅读一下的相关内容。

下面是一个可以编译和运行的代码版本。

==> kamil/main.go

package main

import (
    "kamil/service"
)

type Program struct {
    appleService service.AppleServiceI
}

func main() {
    program := &Program{
        appleService: &service.AppleService{},
    }

    apple := program.appleService.New()
    apple.Eat()
}

==> kamil/service/service.go

package service

import (
    "kamil/service/apple"
)

type AppleServiceI interface {
    New() AppleI
}

type AppleI interface {
    Eat()
}

type AppleService struct {
}

func (a *AppleService) New() AppleI {
    return &apple.Apple{}
}

==> kamil/service/apple/apple.go(未更改)

package apple

import "fmt"

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println("mniam, mniam")
}

话虽如此,我认为你采取的方法不符合Go的惯用方式,你可能会在某个时候遇到困难。

英文:

I think you have a couple of problems here. For one, you have defined the AppleI interface twice, once in main and once in service. Second, you seem to be trying to do Python or Java (I'm guessing) style packages. It's worth reading up on packages

Here is a version of your code that compiles and runs.

==> kamil/main.go

package main

import (
    &quot;kamil/service&quot;
)

type Program struct {
    appleService service.AppleServiceI
}

func main() {
    program := &amp;Program{
        appleService: &amp;service.AppleService{},
    }

    apple := program.appleService.New()
    apple.Eat()
}

==> kamil/service/service.go

package service

import (
    &quot;kamil/service/apple&quot;
)

type AppleServiceI interface {
    New() AppleI
}

type AppleI interface {
    Eat()
}

type AppleService struct {
}

func (a *AppleService) New() AppleI {
    return &amp;apple.Apple{}
}

==> kamil/service/apple/apple.go (unchanged)

package apple

import &quot;fmt&quot;

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println(&quot;mniam, mniam&quot;)
}

That said, I don't think the approach you are taking is idiomatic GO and you will likely end up banging your head against it at some point 接口具有相同的方法,但被视为不同。

答案2

得分: 3

我会回答自己的问题

首先,我刚刚发现了关于为什么在Go中官方FAQ中不认为这些接口是相似的答案:http://golang.org/doc/faq#t_and_equal_interface

为什么类型T不满足Equal接口?

考虑这个简单的接口,表示一个可以与另一个值进行比较的对象:

type Equaler interface {
Equal(Equaler) bool
}

还有这个类型T:

type T int
func (t T) Equal(u T) bool { return t == u } // 不满足Equaler

与某些多态类型系统中的类似情况不同,T不实现Equaler。T.Equal的参数类型是T,而不是字面上所需的Equaler类型。

在Go中,类型系统不会提升Equal的参数;这是程序员的责任,正如类型T2所示,它实现了Equaler:

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) } // 满足Equaler

但即使这样也不像其他类型系统那样,因为在Go中,任何满足Equaler的类型都可以作为参数传递给T2.Equal,并且在运行时我们必须检查参数的类型是否为T2。有些语言会在编译时保证这一点。

还有一个相关的例子:

type Opener interface {
Open() Reader
}

func (t T3) Open() *os.File

在Go中,T3不满足Opener,尽管在其他语言中可能满足。

虽然确实在这些情况下Go的类型系统对程序员的帮助较少,但是子类型的缺失使得关于接口满足的规则非常容易说明:函数的名称和签名是否与接口完全一致?Go的规则也很容易高效地实现。我们认为这些优点抵消了自动类型提升的缺失。如果Go有一天采用某种形式的多态类型,我们预计会有一种方式来表达这些示例的思想,并且还可以对它们进行静态检查。

其次,在我的情况下,我可以使用匿名接口interface { Eat() }而不是声明type ProductI interface { Eat() }

但是在Go FAQ中的示例中不可能这样做。考虑以下代码:

type Equaler interface {
    Equal(Equaler) bool
}

type T int
func (t T) Equal(u Equaler) bool { return t == u.(T) }

如果你在这里使用相同的技巧,你会得到:

type T int
func (t T) Equal(u interface{ Equal(interface{ Equal(...) bool }) bool }) bool { return t == u.(T) }

换句话说,你会陷入递归 - 出错了

下面是我使用这个技巧更新的代码 - 它可以工作 - 有人可能会发现这个技巧有用。

==> ./eater/eater.go <==

package eater

type ProductServiceI interface {
    New() interface { Eat() }
}

type Eater struct {
    productService ProductServiceI
}

func New(productService ProductServiceI) *Eater {
    return &Eater{
        productService: productService,
    }
}

func (a *Eater) EatUntilHappy() {
    for i:=0; i < 5; i++ {
        product := a.productService.New()
        product.Eat()
    }
}

==> ./main.go <==

package main

import "./apple/service"
import "./eater"

func main() {
    appleService := &service.AppleService{}
    appleEater := eater.New(appleService)
    appleEater.EatUntilHappy()
}

==> ./apple/service/service.go <==

package service

import "./apple"

type AppleService struct {
}

func (a *AppleService) New() interface{ Eat() } {
    return &apple.Apple{}
}

==> ./apple/service/apple/apple.go <==

package apple

import "fmt"

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println("mniam, mniam")
}

结果如下:

# go run main.go
mniam, mniam
mniam, mniam
mniam, mniam
mniam, mniam
mniam, mniam
英文:

I will answer my own question

Firstly I just found out answer about why those interfaces are not considered similar in go in their official faq: http://golang.org/doc/faq#t_and_equal_interface

> Why doesn't type T satisfy the Equal interface?
>
> Consider this simple interface to represent an object that can compare
> itself with another value:
>
> type Equaler interface {
> Equal(Equaler) bool }
>
> and this type, T:
>
> type T int func (t T) Equal(u T) bool { return t == u } // does not
> satisfy Equaler
>
> Unlike the analogous situation in some polymorphic type systems, T
> does not implement Equaler. The argument type of T.Equal is T, not
> literally the required type Equaler.
>
> In Go, the type system does not promote the argument of Equal; that is
> the programmer's responsibility, as illustrated by the type T2, which
> does implement Equaler:
>
> type T2 int func (t T2) Equal(u Equaler) bool { return t == u.(T2) }
> // satisfies Equaler
>
> Even this isn't like other type systems, though, because in Go any
> type that satisfies Equaler could be passed as the argument to
> T2.Equal, and at run time we must check that the argument is of type
> T2. Some languages arrange to make that guarantee at compile time.
>
> A related example goes the other way:
>
> type Opener interface { Open() Reader }
>
> func (t T3) Open() *os.File
>
> In Go, T3 does not satisfy Opener, although it might in another
> language.
>
> While it is true that Go's type system does less for the programmer in
> such cases, the lack of subtyping makes the rules about interface
> satisfaction very easy to state: are the function's names and
> signatures exactly those of the interface? Go's rule is also easy to
> implement efficiently. We feel these benefits offset the lack of
> automatic type promotion. Should Go one day adopt some form of
> polymorphic typing, we expect there would be a way to express the idea
> of these examples and also have them be statically checked.

Secondly in my case I can use anonymous interface interface { Eat() } instead of declaring type ProductI interface { Eat() }

But in case of examples from go faq that is not possible. Consider this:

type Equaler interface {
    Equal(Equaler) bool
}

type T int
func (t T) Equal(u Equaler) bool { return t == u.(T) }

If you would use the same trick here you will get

type T int
func (t T) Equal(u interface{ Equal(interface{ Equal(...) bool }) bool }) bool { return t == u.(T) }

In other words you end up with recursion - ups

Below is my updated code with this trick - it works - someone might find this trick useful.

==> ./eater/eater.go <==

package eater

type ProductServiceI interface {
    New() interface { Eat() }
}

type Eater struct {
    productService ProductServiceI
}

func New(productService ProductServiceI) *Eater {
    return &amp;Eater{
        productService: productService,
    }
}

func (a *Eater) EatUntilHappy() {
    for i:=0; i &lt; 5; i++ {
        product := a.productService.New()
        product.Eat()
    }
}

==> ./main.go <==

package main

import &quot;./apple/service&quot;
import &quot;./eater&quot;

func main() {
    appleService := &amp;service.AppleService{}
    appleEater := eater.New(appleService)
    appleEater.EatUntilHappy()
}

==> ./apple/service/service.go <==

package service

import &quot;./apple&quot;

type AppleService struct {
}

func (a *AppleService) New() interface{ Eat() } {
    return &amp;apple.Apple{}
}

==> ./apple/service/apple/apple.go <==

package apple

import &quot;fmt&quot;

type Apple struct {
}

func (a *Apple) Eat() {
    fmt.Println(&quot;mniam, mniam&quot;)
}

And the result 接口具有相同的方法,但被视为不同。

# go run main.go
mniam, mniam
mniam, mniam
mniam, mniam
mniam, mniam
mniam, mniam

答案3

得分: 1

只是为了完整起见:一个非常类似的情况(存在具有相同签名的方法,但Go找不到它们)可能发生在接口中使用私有方法(小写方法名)的情况下。

这样,Go将限制其对方法的搜索范围,仅在定义接口的包中进行搜索,因此无法在实现该方法的包中找到该方法。

实际上,我无法想到在公共接口中使用私有方法的任何合理理由:对于接口的方法使用与接口本身相同的可见性。

英文:

Just for the sake of completion: a very similar case (methods with same signature exists, but Go does not find them) can occur if you use private methods in an interface (lower case method name).

That way Go restricts its searches for the method to the package that defines the Interface and therefore does not find the method inside the package that implements the method.

Actually, as I cannot think of any sane reason to use private methods in a public interface: use the same visibility for the methods of an interface as for the interface itself.

huangapple
  • 本文由 发表于 2014年1月16日 23:35:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/21166113.html
匿名

发表评论

匿名网友

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

确定