Pre-defining some behaviors of objects

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.

One way could be to turn it around and embed the interface instead of the Animal:

https://play.golang.org/p/q64OvH4x0W

It’s morally equivalent to your function pointer, except you get a type where you can implement all the doggy behavior etc. The prepare() step could also be lazy init by the ShowExcitement method etc.

2 Likes

Thanks, I like this approach!

I’m also starting to like separating a raw/unpreparred type from the prepared one, just to put Go’s strict type checking to further use. This is your example with a raw type separated out (PotentialAnimal):
https://play.golang.org/p/DypRWw8D-1

(I’m still just a little shakey on when I should use pointers in Go.)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.