如何处理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
如何处理这样的情况以及如何避免重复 在这种情况下的方法?
答案 0 :(得分:8)
执行此操作的方法是显式提供所需的方法,而不是使用简写语法:
type Entertainer interface {
Hello()
Joke()
Jump()
}
这可能看起来像代码重复,但请注意重复的代码在Go中并不是非典型的东西,特别是当它导致更清晰的代码时。
另请注意:如果您考虑使用其他语言的典型继承,看起来您可能会丢失一些信息,因为您没有记录Entertainer
的事实从Person
继承。但Go接口纯粹是结构化的,没有继承。由于Entertainer
采用Hello()
方法,因此无论您是否明确提及Entertainer
Person
声明Person
,每个Entertainer
都自动为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)
}
传递给其中任何一个:
Person
Here's a link to a Go Playground with the above code
最后一件事 - 你可以争论这样的事情:“但是如果我后来对func JumperSayHello(j Jumper) {
PersonSayHello(j)
}
进行了更改,它将不会反映在其他界面中。”确实如此,你必须手动进行这样的调整,但编译器会让你知道它。
如果你有这个功能:
Person
您的代码可以正常运行。但是,如果向Jumper
添加另一个方法,则依赖于Person
为type Person interface {
Hello()
Think()
}
的事实的代码将不再编译。与
Jumper
你得到了
.\main.go:18: cannot use j (type Jumper) as type Person in argument to PersonSayHello: Jumper does not implement Person (missing Think method)
只要你有任何地方的代码,依赖于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() {
}
,无论你对这些接口做了什么改变,但这个事实实际上并没有在任何地方使用,你总是可以为此目的创建代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
</head>
<body ng-app="myApp">
<my-custom-element ng-click="runAlert()"></my-custom-element>
</body>
</html>
答案 1 :(得分:2)
我认为不可能这样做。 IMO,界面嵌入只是直接在那里使用这些功能的简写。所以它等同于具有两个Hello()
函数。因此来自编译器的错误。
答案 2 :(得分:0)
后跟“ 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 }
但实际上第一个是错误:“重复方法:字符串”。
Go 1.14(五年后的2020年第一季度)可能包括一些改进:
CL 190258: allow embedding overlapping interfaces,替换为CL 191257
如果嵌入式方法具有与现有方法相同的签名,请从嵌入式接口中静默删除它们。
此调整并没有完全调整以前的仅基于语法的方法集计算,在此方法中方法没有签名信息(因此,根据新规则进行重复数据删除会有些棘手,无法正确执行),此更改完全重写了接口方法集计算,从
cmd/compiler
的实现中获取一页。在第一遍中,当收集类型检查接口,显式方法和嵌入式接口,但未“扩展”接口时,也就是说,最终的方法集计算是延迟进行的,无论是在方法查找时还是在需要时, 在类型检查结束时。
规范已更新(CL 190378)
看到一些examples here。