How to deal with duplicate methods in Go interface?

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.

Tags:

Interface

Go