💻 Tech
Go doesn’t have a context of subtypes that if you embed a struct into a struct, the latter will become a subtype of the former.
The example below is a bad design and has syntax error.
type Animal struct {
Nome string
Idade int
}
type Cachorro struct {
Animal
}
type Gato struct {
Animal
}
func (c *Cachorro) Fala() {
fmt.Printf("Woof!")
}
func (g *Gato) Fala() {
fmt.Printf("Meow!")
}
// ...
func main() {
// syntax error
animais := []Animal{
Cachorro: Animal{
Nome: "Pluto",
Idade: 1,
},
Gato: Animal{
Nome: "Tom",
Idade: 2,
},
}
for _, animal := range animais {
animal.Fala()
}
}
The above code is a bad design because it’s creating Animal
struct that’s only embedded to maintain state. Furthermore, it assumes subtyping, that’s why it’s a syntax error in the slice of Animal part.
A rule of thumb in grouping types is determining what it does or the behavior. In the example above, they all speak, so we can create an interface.
type Acao interface {
Fala()
}
We then eliminate the use of Animal
struct. Each animal can maintain their own state. After creating objects from those animals, we can then use the interface to group the types.
type Cachorro struct {
Nome string
Idade int
}
type Gato struct {
Nome string
Idade int
}
func (c *Cachorro) Fala() {
fmt.Printf("Woof!")
}
func (g *Gato) Fala() {
fmt.Printf("Meow!")
}
// ...
func main() {
animais := []Acao{
Cachorro: Animal{
Nome: "Pluto",
Idade: 1,
},
Gato: Animal{
Nome: "Tom",
Idade: 2,
},
}
for _, animal := range animais {
animal.Fala()
}
}
In summary, we group the types based on what they do (i.e., behavior). One basic principle in Go is not to design interface upfront but to create them as they become necessary.