How to deal with duplicate methods in Go interface?
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")
}
The following error occurs if I run this code.
$ go run foo.go
# command-line-arguments
./foo.go:24: duplicate method Hello
How to deal with situations like this and how can we avoid duplicate methods in such a scenario?
The way to do this is to explicitly provide the required methods instead of using the shorthand syntax:
type Entertainer interface {
Hello()
Joke()
Jump()
}
This may seem like code duplication, but note that duplicate code isn't an untypical thing in Go, especially when it leads to clearer code.
Also note this: If you think in terms of typical inheritance in other languages, it may seem like you're losing some information by doing this, because you're not recording the fact that Entertainer
inherits from, say, Person
. But Go interfaces are purely structural, there is no inheritance. Because an Entertainer
has a Hello()
method, every Entertainer
is automatically a Person
, whether or not you explicitly mention Person
insided the Entertainer
declaration.
All of this compiles without problems (except for a "declared and not used" error) even when you don't use the shorthand syntax for any of the interfaces:
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
Here's a complete program that compiles and runs just fine. Given these declarations:
package main
import (
"fmt"
)
type Person interface {
Hello()
}
type Joker interface {
Hello()
Joke()
}
type Jumper interface {
Hello()
Jump()
}
type Entertainer interface {
Hello()
Joke()
Jump()
}
let's create a Clown
type:
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")
}
A Clown
can greet, jump, and joke, and so it implements all of our interfaces. Given these four functions:
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()
}
you can pass a Clown
to any of them:
func main() {
c := Clown{}
PersonSayHello(c)
JokerJoke(c)
JumperJump(c)
EntertainerEntertain(c)
}
Here's a link to a Go Playground with the above code.
One final thing – you could argue something like this: "But if I later make a change to Person
, it won't be reflected in the other interfaces." It's true, you have to make such an adjustment manually, but the compiler will let you know about it.
If you have this function:
func JumperSayHello(j Jumper) {
PersonSayHello(j)
}
your code will work without any issues. But if you add another method to Person
, code that relies on the fact that a Jumper
is a Person
will no longer compile. With
type Person interface {
Hello()
Think()
}
you get
.\main.go:18: cannot use j (type Jumper) as type Person in argument to PersonSayHello: Jumper does not implement Person (missing Think method)
This will be the case as long as you have code anywhere that relies on the fact that a Jumper
is always a Person
. And if you don't, not even in your tests, then – well, maybe it doesn't actually matter that the jumper doesn't think?
But if for whatever reason you actually need to ensure that a Jumper
is always a Person
, no matter what changes you make to these interfaces, but this fact isn't actually used anywhere, you can always create code just for this purpose:
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() {
}
I don't think it is possible to do this. IMO, interface embedding is just a shorthand for having those functions directly there. So it is equivalent as having two Hello()
functions. Hence the error from compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With