0

Given the following Go code:

package main

type CatToy interface {
    Rattle() string
}

type Cat struct {
}

func (cat *Cat) Play(catToy CatToy) {
    println("The cat is playing!", catToy.Rattle())
}

type DogToy interface {
    Roll() string
}

type Dog struct {
}

func (dog *Dog) Play(dogToy DogToy) {
    println("The dog is playing!", dogToy.Roll())
}

type SuperToy struct {
}

func (toy *SuperToy) Rattle() string {
    return "Rattle!!!"
}

func (toy *SuperToy) Roll() string {
    return "Rolling..."
}

type Pet interface {
    Play(toy interface{})
}

func main() {
    cat := &Cat{}
    dog := &Dog{}
    superToy := &SuperToy{}

    // Working
    cat.Play(superToy)
    dog.Play(superToy)

    // Not Working
    pets := []Pet{cat, dog}
    for _, pet := range pets {
        pet.Play(superToy)
    }
}

I am getting these errors:

# command-line-arguments
./main.go:65:16: cannot use cat (type *Cat) as type Pet in array or slice literal:
    *Cat does not implement Pet (wrong type for Play method)
        have Play(CatToy)
        want Play(interface {})
./main.go:65:21: cannot use dog (type *Dog) as type Pet in array or slice literal:
    *Dog does not implement Pet (wrong type for Play method)
        have Play(DogToy)
        want Play(interface {})

The SuperToy implements both CatToy and DogToy. However, when I create an interface Pet with interface as an argument, I get an error. May I know how I'll be able to get an array/slice with a cat and dog inside? I want to iterate through this slice and call a function for each one. I also want to keep the CatToy and DogToy interfaces. I'm also ok with removing the Pet interface.

More info: In the future, it's more likely that I'll add more pets. I don't think that I'll add more actions such as Play.

Thank you

Abakada
  • 141
  • 2
  • 9
  • 2
    Go offers no inheritance bases object orientation and trying to simulate it is doomed to fail. Don't try, you'll hurt yourself. – Volker Aug 12 '18 at 19:18
  • @Volker May I know the idiomatic way to implement this in Go? – Abakada Aug 12 '18 at 19:29
  • 1
    Well, you redesign. You pack common functionality into an interface. You use normal functions. – Volker Aug 13 '18 at 04:57
  • 1
    After reading this http://spf13.com/post/is-go-object-oriented/, it seems that inheritance is the only thing that needs to be avoided. – Abakada Aug 13 '18 at 12:01

3 Answers3

3

I get what you are trying to do, but it's impossible: your Cat and Dog types do not implement the Pet interface, as their Play methods take different types, so you won't be able to just call Play on them with your SuperToy.

To fix this, you would need to create a Toy interface that has both the Roll and the Rattle methods, and make Pet.Play, Cat.Play and Dog.Play take this interface as an argument.

package main

type Cat struct {
}

func (cat *Cat) Play(catToy Toy) {
    println("The cat is playing!", catToy.Rattle())
}

type Dog struct {
}

func (dog *Dog) Play(dogToy Toy) {
    println("The dog is playing!", dogToy.Roll())
}

type Toy interface {
    Roll() string
    Rattle() string
}

type SuperToy struct {
}

func (toy *SuperToy) Rattle() string {
    return "Rattle!!!"
}

func (toy *SuperToy) Roll() string {
    return "Rolling..."
}

type Pet interface {
    Play(toy Toy)
}

func main() {
    cat := &Cat{}
    dog := &Dog{}
    superToy := &SuperToy{}

    // Working
    cat.Play(superToy)
    dog.Play(superToy)

    // Not Working
    pets := []Pet{cat, dog}
    for _, pet := range pets {
        pet.Play(superToy)
    }
}

Gives the output

The cat is playing! Rattle!!!
The dog is playing! Rolling...
The cat is playing! Rattle!!!
The dog is playing! Rolling...
Abakada
  • 141
  • 2
  • 9
Ullaakut
  • 3,554
  • 2
  • 19
  • 34
  • I would really like to keep the `CatToy` and `DogToy` interfaces, because I'm planning to extract `Cat` and `Dog` to separate packages/projects. Is there another alternative? – Abakada Aug 12 '18 at 19:49
  • 2
    I'm afraid not, as Volker mentioned in his comment, Go does not support inheritance and what you are trying to do it clearly translating your object-oriented habits into Go code. – Ullaakut Aug 12 '18 at 20:19
0

You could get the Play methods to take an interface{} and then do a type assertion inside the method:

func (dog *Dog) Play(toy interface{}) {
    dogToy, isDogToy := toy.(DogToy)
    if !isDogToy {
        println("The dog does not know what to do with this toy!")
        return
    }
    println("The dog is playing!", dogToy.Roll())
}

Full executable example on Go Playground:

https://play.golang.org/p/LZZ-HqpzR-Z

Iain Duncan
  • 3,139
  • 2
  • 17
  • 28
  • Thanks for the alternative solution! However, `type checking` is commonly a code smell so I want to avoid using it. – Abakada Aug 12 '18 at 22:26
0

Here's another working solution:

package main

type CatToy interface {
    Rattle() string
}

type Cat struct {
    Toy CatToy
}

func (cat *Cat) Play() {
    println("The cat is playing!", cat.Toy.Rattle())
}

type DogToy interface {
    Roll() string
}

type Dog struct {
    Toy DogToy
}

func (dog *Dog) Play() {
    println("The dog is playing!", dog.Toy.Roll())
}

type SuperToy struct {
}

func (toy *SuperToy) Rattle() string {
    return "Rattle!!!"
}

func (toy *SuperToy) Roll() string {
    return "Rolling..."
}

type Pet interface {
    Play()
}

func main() {
    superToy := &SuperToy{}
    cat := &Cat{superToy}
    dog := &Dog{superToy}

    // Working
    cat.Play()
    dog.Play()

    // Working also
    pets := []Pet{cat, dog}
    for _, pet := range pets {
        pet.Play()
    }
}

This solution keeps the Cat + CatToy and Dog + DogToy independent from the main + SuperToy. This allows extraction to separate packages.

However, I do somewhat agree with @Volker and @Ullaakut. This solution seems not idiomatic for Go. I'm still not sure about the proper solution.

Abakada
  • 141
  • 2
  • 9