Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with duplicate methods in Go interface?

Tags:

interface

go

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?

like image 873
Lone Learner Avatar asked May 02 '17 04:05

Lone Learner


2 Answers

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() {
}
like image 108
ij6 Avatar answered Oct 14 '22 11:10

ij6


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.

like image 24
Navaneeth K N Avatar answered Oct 14 '22 10:10

Navaneeth K N