I've attempted to lookup objects inside a interface slice using the type of the object. This current solution I have looks as follows:
package main
import (
"errors"
"fmt"
)
type Entity struct {
children []Childable
}
func (e *Entity) ChildByInterface(l interface{}) (Childable, error) {
for _, c := range e.children {
if fmt.Sprintf("%T", c) == fmt.Sprintf("%T", l) {
return c, nil
}
}
return nil, errors.New("child doesn't exist")
}
type Childable interface {
GetName() string
}
func main() {
ent := &Entity{
[]Childable{
&Apple{name: "Appy"},
&Orange{name: "Orry"},
// more types can by introduced based on build tags
},
}
appy, err := ent.ChildByInterface(&Apple{})
if err != nil {
fmt.Println(err)
} else {
appy.(*Apple).IsRed()
fmt.Printf("%+v", appy)
}
}
type Apple struct {
name string
red bool
}
func (a *Apple) GetName() string {
return a.name
}
func (a *Apple) IsRed() {
a.red = true
}
type Orange struct {
name string
yellow bool
}
func (o *Orange) GetName() string {
return o.name
}
func (o *Orange) IsYellow() {
o.yellow = true
}
https://play.golang.org/p/FmkWILBqqA-
More Childable types (Apple, Orange, ...) can be injected using build tags. So in order to keep the lookup type safe and avoid mistakes, I'm passing an interface{}
to the lookup function. The Childable interface also assures the newly injected types implement the correct functions.
This is where things start to get messy. Currently I'm doing a string comparison on both the interface's type and the Childable object's type to see if they match: fmt.Sprintf("%T", c) == fmt.Sprintf("%T", l)
Then I can still only return the Childable interface. So I have to use type assertion to get the correct type: appy.(*Apple)
The boiler plating is to get the child in its correct type has become very tedious and the string comparison to find a match is having a significant performance impact. What better solution can I use to match two interfaces with each other to avoid the performance knock?
As far fmt.Sprintf("%T", c)
used reflect
under the hood there are no advantages to imply it - better use reflect
directly. You can use reference argument as a placeholder of a result instead of return value.
func (e *Entity) ChildByInterface(l Childable) error {
for _, c := range e.children {
if reflect.TypeOf(l) == reflect.TypeOf(c) {
fmt.Println(c)
reflect.ValueOf(l).Elem().Set(reflect.ValueOf(c).Elem())
return nil
}
}
return errors.New("child doesn't exist")
}
Now pass a placeholder
apple := &Apple{}
err := ent.ChildByInterface(apple)
//and use it
apple.IsRed()