【问题标题】:How to deal with duplicate methods in Go interface?如何处理 Go 接口中的重复方法?
【发布时间】:2017-09-29 12:38:28
【问题描述】:

如何处理Go接口中的重复方法?

package main

import (
    "fmt"
)

type Person interface {
    Hello()
}

type Joker interface {
    Person
    Joke()
}

type Jumper interface {
    Person
    Jump()
}

type Entertainer interface {
    Joker
    Jumper
}

func main() {
    fmt.Println("hello, world")
}

如果我运行此代码会出现以下错误。

$ go run foo.go
# command-line-arguments
./foo.go:24: duplicate method Hello

如何处理此类情况以及如何避免重复 这种情况下的方法?

【问题讨论】:

    标签: go interface


    【解决方案1】:

    我认为不可能做到这一点。 IMO,接口嵌入只是直接在那里拥有这些功能的简写。所以它相当于有两个Hello() 函数。因此来自编译器的错误。

    【讨论】:

      【解决方案2】:

      这样做的方法是显式提供所需的方法,而不是使用简写语法:

      type Entertainer interface {
          Hello()
          Joke()
          Jump()
      }
      

      这看起来像是代码重复,但请注意,重复代码在 Go 中并不是不常见的事情,尤其是当它导致代码更清晰时。

      还要注意这一点:如果您考虑其他语言的典型继承,这样做似乎会丢失一些信息,因为您没有记录Entertainer 继承 来自,比如说,Person。但是 Go 接口是纯结构的,没有继承。因为Entertainer 具有Hello() 方法,所以每个Entertainer 都自动成为Person,无论您是否在Entertainer 声明中明确提及Person

      即使您不使用任何接口的简写语法,所有这些都可以毫无问题地编译(“已声明且未使用”错误除外):

      var e Entertainer
      var ju Jumper
      var jo Joker
      var p Person
      
      p = e    // every Entertainer is also a Person
      p = ju   // every Jumper is also a Person
      p = jo   // every Joker is also a Person
      
      ju = e   // every Entertainer is also a Jumper
      
      jo = e   // every Entertainer is also a Joker
      

      这是一个完整的程序,可以正常编译和运行。鉴于这些声明:

      package main
      
      import (
          "fmt"
      )
      
      type Person interface {
          Hello()
      }
      
      type Joker interface {
          Hello()
          Joke()
      }
      
      type Jumper interface {
          Hello()
          Jump()
      }
      
      type Entertainer interface {
          Hello()
          Joke()
          Jump()
      }
      

      让我们创建一个Clown 类型:

      type Clown struct {}
      
      func (c Clown) Hello() {
          fmt.Println("Hello everybody")
      }
      
      func (c Clown) Joke() {
          fmt.Println("I'm funny")
      }
      
      func (c Clown) Jump() {
          fmt.Println("And up I go")
      }
      

      Clown 可以打招呼、跳跃和开玩笑,因此它实现了我们所有的接口。鉴于这四个功能:

      func PersonSayHello(p Person) {
          p.Hello()
      }
      
      func JumperJump(j Jumper) {
          j.Jump()
      }
      
      func JokerJoke(j Joker) {
          j.Joke()
      }
      
      func EntertainerEntertain(e Entertainer) {
          e.Joke()
          e.Jump()
      }
      

      您可以将Clown 传递给他们中的任何一个:

      func main() {
          c := Clown{}
      
          PersonSayHello(c)
          JokerJoke(c)
          JumperJump(c)
          EntertainerEntertain(c)
      }
      

      Here's a link to a Go Playground with the above code.

      最后一件事——你可以这样争论:“但如果我稍后对Person 进行更改,它不会反映在其他界面中。”确实,你必须手动进行这样的调整,但是编译器会让你知道的。

      如果你有这个功能:

      func JumperSayHello(j Jumper) {
          PersonSayHello(j)
      }
      

      您的代码将毫无问题地运行。但是,如果您向Person 添加另一个方法,则依赖于JumperPerson 这一事实的代码将不再编译。与

      type Person interface {
          Hello()
          Think()
      }
      

      你得到

      .\main.go:18: 不能在 PersonSayHello 的参数中使用 j(类型 Jumper)作为 Person 类型:
              Jumper 没有实现 Person(缺少 Think 方法)

      只要您的代码任何地方 都依赖于Jumper 始终是Person 这一事实,就会出现这种情况。如果你不这样做,甚至在你的测试中,那么——好吧,也许跳线不思考实际上并不重要?

      但是,如果出于某种原因您确实需要确保 Jumper 始终是 Person,无论您对这些接口进行什么更改,但实际上并没有在任何地方使用这一事实,您始终可以创建代码仅出于此目的:

      package main
      
      type Person interface {
          Hello()
      }
      
      type Jumper interface {
          Hello()
          Jump()
      }
      
      // this function is never used, it just exists to ensure
      // interface compatibility at compile time
      func ensureJumperIsPerson(j Jumper) {
          var p Person = j
          _ = p
      }
      
      func main() {
      }
      

      【讨论】:

        【解决方案3】:

        后面是“issue 6997, proposal: spec: allow embedding overlapping interfaces”。

        如果您将接口视为对实现类型的一组约束,那么 结合两个接口(不相互不兼容),例如:

        type I interface { f(); String() string }
        type J interface { g(); String() string } 
        

        有一个自然的解释,相当于一个包含联合的接口 这样的约束。例如这些应该是等价的:

        type IJ interface { I; J }
        type IJ interface { f(); g(); String() string }
        

        但其实第一个是错误:“duplicate method: String”。

        Go 1.14(2020 年第一季度,五年后)可能包括一些改进:

        CL 190258: allow embedding overlapping interfaces,替换为CL 191257

        如果嵌入式接口与现有方法具有相同的签名,则从嵌入式接口中悄悄删除重复的方法。

        此更改完全重写了接口,而不是调整先前仅基于语法的方法集计算,其中方法没有签名信息(因此根据新规则进行重复数据删除会有些棘手)方法集计算,从cmd/compiler 的实现中获取一页。

        在第一遍中,当类型检查接口、显式方法和嵌入式接口被收集时,但接口没有“扩展”,即最终方法集计算是惰性完成的,无论是在需要方法查找时,还是 在类型检查结束时。

        规范已更新 (CL 190378)

        查看一些examples here

        【讨论】:

          猜你喜欢
          • 2021-12-12
          • 1970-01-01
          • 2021-08-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-17
          相关资源
          最近更新 更多