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):
", animal.Name, reflect.ValueOf(animal).Type().Elem().Name())
for voice := 0; voice < strength; voice++ {
fmt.Printf("%s ", sound)
}
fmt.Println("
")
}
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):
", 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):
", animal.Name, animalType)
for voice := 0; voice < strength; voice++ {
fmt.Printf("%s ", sound)
}
fmt.Println("
")
}
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.