-1

I'm currently trying myself on some OOP-esque Go, following a tutorial I found online.

So far, it's quite fascinating (reminds me of trying to force OOP into ANSI-C).

However, there's just one thing bothering me that I can't seem to solve.

How would I be able to reflect the Type name of the embedding struct?

All info I found online says one can't reflect over the embedding struct, as the embedded struct has no direct access to it.

Is this entirely accurate? If so, what would be the correct way to solve the following problem (code below)?

Basically, the program prints out the name of three individual animals, followed by the Type name of the embedded struct in parentheses, followed by the respective animal's "sound".

i e for the dog named "Rover", it will print "Rover (Animal): BARK BARK".

Now, obviously, "Rover (Animal)" isn't particularly informative. Ideally, this should be "Rover (Dog)" (the Type name of the embedding struct, rather than the embedded struct).

Therein lies my problem. How may I be able to reflect the Type of the embedding struct, so "Rover (Animal)" becomes "Rover ("Dog"), "Julius (Animal)" becomes "Julius (Cat)", etc?

package main

import (
    "fmt"
    "reflect"
)

type Animal struct {
    Name string
    mean bool
}

type AnimalSounder interface {
    MakeNoise()
}

type Dog struct {
    Animal
    BarkStrength int
}

type Cat struct {
    Basics       Animal
    MeowStrength int
}

type Lion struct {
    Basics       Animal
    RoarStrength int
}

func (dog *Dog) MakeNoise() {
    dog.PerformNoise(dog.BarkStrength, "BARK")
}

func (cat *Cat) MakeNoise() {
    cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}

func (lion *Lion) MakeNoise() {
    lion.Basics.PerformNoise(lion.RoarStrength, "ROAR!!  ")
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

func main() {
    myDog := &Dog{
        Animal{
            Name: "Rover", // Name
            mean: false,   // mean
        },
        2, // BarkStrength
    }

    myCat := &Cat{
        Basics: Animal{
            Name: "Julius",
            mean: true,
        },
        MeowStrength: 3,
    }

    wildLion := &Lion{
        Basics: Animal{
            Name: "Aslan",
            mean: true,
        },
        RoarStrength: 5,
    }

    MakeSomeNoise(myDog)
    MakeSomeNoise(myCat)
    MakeSomeNoise(wildLion)
}

func (animal *Animal) PerformNoise(strength int, sound string) {
    if animal.mean == true {
        strength = strength * 5
    }

    fmt.Printf("%s (%s): \n", animal.Name, reflect.ValueOf(animal).Type().Elem().Name())

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Println("\n")
}
user237251
  • 211
  • 1
  • 10
  • 1
    It is entirely accurate, there's no inheritance in Go, the embedded struct `Animal` has no knowledge of the embedding struct `Dog`. I don't think it's particularly smart to try to force classical OOP on a language that isn't designed in such a way. – mkopriva Apr 19 '18 at 07:47
  • Thanks. So what would be the alternative in that case? The only thing I can think of is to extend "PerformNoise()" so it expects an "animal Type name" and then adding those to all implementations of "MakeNoise()" (the "AnimalSounder" interface method). So dog.PerformNoise(dog.BarkStrength, "BARK") would become dog.PerformNoise(dog.BarkStrength, "BARK", reflect.ValueOf(dog).Type().Elem().Name()) or just dog.PerformNoise(dog.BarkStrength, "BARK", "Dog"). – user237251 Apr 19 '18 at 08:06
  • 2
    @user237251 That sounds about right. For similar questions, see [one](https://stackoverflow.com/questions/36710259/go-ensuring-embedded-structs-implement-interface-without-introducing-ambiguity/36711554#36711554); [two](https://stackoverflow.com/questions/30622605/can-embedded-struct-method-have-knowledge-of-parent-child/30629132#30629132); [three](https://stackoverflow.com/questions/29390736/go-embedded-struct-call-child-method-instead-parent-method/29390981#29390981). – icza Apr 19 '18 at 08:13
  • 1
    Yes, it's true. The proper approach is not to do OOP in Go. – Jonathan Hall Apr 19 '18 at 09:12

1 Answers1

-1

Alright.

Answering (or at least attempting to) my own question then, providing what I think is probably the right way to deal with this (at least the most straightforward one).

For brevity I changed all versions of

func (cat/dog/lion *Cat/*Dog/*Lion) MakeNoise(){}

to

func (animal *Cat/*Dog/*Lion) MakeNoise(){}

As far as I can tell, that shouldn't really hurt readability, nor introduce any side-effects.

All iterations of "MakeNoise()" now simply provide a third parameter that is identical to the embedding struct's Type name as a string.

"PerformNoise()" accepts that parameter ("animalType") and simply appends it to the output

i e

fmt.Printf("%s (%s): \n", animal.Name, animalType)

The full, updated code:

package main

import (
    "fmt"
    "reflect"
)

type Animal struct {
    Name string
    Type string
    mean bool
}

type AnimalSounder interface {
    MakeNoise()
}

type Dog struct {
    Animal
    BarkStrength int
}

type Cat struct {
    Basics       Animal
    MeowStrength int
}

type Lion struct {
    Basics       Animal
    RoarStrength int
}

func (animal *Dog) MakeNoise() {
    animal.PerformNoise(animal.BarkStrength, "BARK", reflect.ValueOf(animal).Type().Elem().Name())
}

func (animal *Cat) MakeNoise() {
    animal.Basics.PerformNoise(animal.MeowStrength, "MEOW", reflect.ValueOf(animal).Type().Elem().Name())
}

func (animal *Lion) MakeNoise() {
    animal.Basics.PerformNoise(animal.RoarStrength, "ROAR!!  ", reflect.ValueOf(animal).Type().Elem().Name())
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

func main() {
    myDog := &Dog{
        Animal{
            Name: "Rover", // Name
            mean: false,   // mean
        },
        2, // BarkStrength
    }

    myCat := &Cat{
        Basics: Animal{
            Name: "Julius",
            mean: true,
        },
        MeowStrength: 3,
    }

    wildLion := &Lion{
        Basics: Animal{
            Name: "Aslan",
            mean: true,
        },
        RoarStrength: 5,
    }

    MakeSomeNoise(myDog)
    MakeSomeNoise(myCat)
    MakeSomeNoise(wildLion)
}

func (animal *Animal) PerformNoise(strength int, sound string, animalType string) {
    if animal.mean == true {
        strength = strength * 5
    }

    fmt.Printf("%s (%s): \n", animal.Name, animalType)

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Println("\n")
}

PS: Just to re-iterate. I wouldn't want to write all my code in this "fake" OOP sort of fashion.

It adds a boatload of unnecessary abstraction and the need for new design considerations to the process.

However, I do think it's a nice way to experiment with the language's basic feature-set.

user237251
  • 211
  • 1
  • 10
  • no point of using reflect there since in each method you know the receiver type/name. – mkopriva Apr 19 '18 at 08:34
  • You mean via Printf/SprintF? – user237251 Apr 19 '18 at 09:01
  • https://play.golang.org/p/eLM_qKQN56K reflection is better avoided if it is not necessary, it has its uses but your example is not one of it. – mkopriva Apr 19 '18 at 09:04
  • Oh. I figured. And using Sprintf() would've also added additional parsing overhead on top of reflection overhead. But again. If it was about efficiency, I wouldn't experiment with hammering OOP into a non-OOP language ;) – user237251 Apr 19 '18 at 09:08
  • OO doesn't mean "inheritance". Go is absolutely OO, it supports both encapsulation of behavior and method polymorphism via interfaces. OO doesn't mean Java classes. In fact you are forced to use OOP everytime you use Go io stream interface. – mpm Apr 19 '18 at 09:52
  • Polymorphism is not necessary restricted to OOP. Modern functional programming languages endorse polymorphism, too. Rust does. – user237251 Apr 21 '18 at 04:11