I have a project that needs to load several “definitions” from a JSON file. Each definition is an object with properties that help describe how some piece of data can be encoded/decoded/manipulated. I’m trying to find a good general approach, and since I’m relatively new to Go I figured I could really use some expert opinions. Here’s a kind of brutish approach:
package main
import "fmt"
type Animal struct {
Subtype string
Name string
}
func (a Animal) ShowExcitementSlowVersion() {
switch a.Subtype {
case "dog":
fmt.Printf("%s barks\n", a.Name)
case "cat":
fmt.Printf("%s meows\n", a.Name)
default:
fmt.Println("This is bad...")
}
}
func main() {
animals := []Animal{
Animal{"dog", "Fido"},
Animal{"cat", "Misty"},
} // Imagine this list comes from some big JSON file
for _, animal := range animals {
fmt.Printf("Here is a %s named %s\n", animal.Subtype, animal.Name)
animal.ShowExcitementSlowVersion()
}
}
There are at least two problems with this.
- In my real situation I need to call
ShowExcitementSlowVersion()
millions of times over and I would like to avoid the (small) execution cost of the switch statement (which is greatly simplified down to a simple switch in this example). - I would need to write nearly-duplicate code to do validation ahead of time. The “This is bad…” message shows up at an inappropriate time.
The best solution I can come up with is this. But it still has some hairiness.
package main
import "fmt"
type Animalish interface {
ShowExcitement()
ToAnimal() *Animal
}
type Animal struct {
Subtype string
Name string
}
func (a Animal) ToAnimal() *Animal {
return &a // Really?! It's like an inefficient no-op
}
type Dog struct {
Animal
}
type Cat struct {
Animal
}
func (d Dog) ShowExcitement() {
fmt.Printf("%s barks\n", d.Name)
}
func (c Cat) ShowExcitement() {
fmt.Printf("%s meows\n", c.Name)
}
func prepare(a Animal) (Animalish, error) {
switch a.Subtype {
case "dog":
return Dog{a}, nil
case "cat":
return Cat{a}, nil
default:
return nil, fmt.Errorf("Unknown animal type %s", a.Subtype)
}
}
func main() {
animals := []Animal{ // again from JSON unmarshall
Animal{"dog", "Fido"},
Animal{"cat", "Misty"},
}
for _, animal := range animals {
prepared, err := prepare(animal)
if err != nil {
panic(err)
}
fmt.Printf("Here is a %s named %s\n", prepared.ToAnimal().Subtype, prepared.ToAnimal().Name)
prepared.ShowExcitement()
}
}
I like this except it seems clumsy and slow to access attributes directly with my ToAnimal
method. I could add individual getters, but I’d rather not have to. I do like how the prepare
method happens at the right time, after JSON unmarshalling.
How can I do better? I thought about putting a function pointer in the Animal struct that prepare() assigns to, which should work great except you’d have this ugliness:
animal.PreparedShowExcitement(animal) // just looks wrong
But maybe that’s a reasonable tradeoff to avoid profusion of types and the akward interface.