oop - Go: Ensuring embedded structs implement interface without introducing ambiguity -
i'm trying clean code base doing better job defining interfaces , using embedded structs reuse functionality. in case have many entity types can linked various objects. want define interfaces capture requirements , structs implement interfaces can embedded entities.
// entities implement interface type entity interface { identifier() type() } // interface entities can link foos type foolinker interface { linkfoo() } type foolinkerentity struct { foo []*foo } func (f *foolinkerentity) linkfoo() { // issue: need access identifier() , type() here // foolinkerentity doesn't implement entity } // interface entities can link bars type barlinker interface { linkbar() } type barlinkerentity struct { bar []*bar } func (b *barlinkerentity) linkbar() { // issues: need access identifier() , type() here // barlinkerentity doesn't implement entity } so first thought have foolinkerentity , barlinkerentity implement entity interface.
// implementation of entity interface type entitymodel struct { id string object string } func (e *entitymodel) identifier() { return e.id } func (e *entitymodel) type() { return e.type } type foolinkerentity struct { entitymodel foo []*foo } type barlinkerentity struct { entitymodel bar []*bar } however, ends ambiguity error types can link both foos , bars.
// baz.identifier() ambiguous between entitymodel, foolinkerentity, // , barlinkerentity. type baz struct { entitymodel foolinkerentity barlinkerentity } what's correct go way structure type of code? type assertion in linkfoo() , linkbar() identifier() , type()? there way check @ compile time instead of runtime?
go not (quite) object oriented langauge: not have classes , does not have type inheritance; supports similar construct called embedding both on struct level , on interface level, , have methods.
so should stop thinking in oop , start thinking in composition. since said in comments foolinkerentity never used on own, helps achieve want in clean way.
i use new names , less functionality concentrate on problem , solution, results in shorter code , easier understand.
the full code can viewed , tested on go playground.
entity
the simple entity , implementation this:
type entity interface { id() int } type entityimpl struct{ id int } func (e *entityimpl) id() int { return e.id } foo , bar
in example foolinkerentity , barlinkerentity decorators, don't need embed (extend in oop) entity, , implementations don't need embed entityimpl. however, since want use entity.id() method, need entity value, may or may not entityimpl, let's not restrict implementation. may choose embed or make "regular" struct field, doesn't matter (both works):
type foo interface { sayfoo() } type fooimpl struct { entity } func (f *fooimpl) sayfoo() { fmt.println("foo", f.id()) } type bar interface { saybar() } type barimpl struct { entity } func (b *barimpl) saybar() { fmt.println("bar", b.id()) } using foo , bar:
f := fooimpl{&entityimpl{1}} f.sayfoo() b := barimpl{&entityimpl{2}} b.saybar() output:
foo 1 bar 2 foobarentity
now let's see "real" entity entity (implements entity) , has both features provided foo , bar:
type foobarentity interface { entity foo bar sayfoobar() } type foobarentityimpl struct { *entityimpl fooimpl barimpl } func (x *foobarentityimpl) sayfoobar() { fmt.println("foobar", x.id(), x.fooimpl.id(), x.barimpl.id()) } using foobarentity:
e := &entityimpl{3} x := foobarentityimpl{e, fooimpl{e}, barimpl{e}} x.sayfoo() x.saybar() x.sayfoobar() output:
foo 3 bar 3 foobar 3 3 3 foobarentity round #2
if foobarentityimpl not need know (does not use) internals of entity, foo , bar implementations (entityimpl, fooimpl , barimpl in our cases), may choose embed interfaces , not implementations (but in case can't call x.fooimpl.id() because foo not implement entity - implementation detail our initial statement don't need / use it):
type foobarentityimpl struct { entity foo bar } func (x *foobarentityimpl) sayfoobar() { fmt.println("foobar", x.id()) } its usage same:
e := &entityimpl{3} x := foobarentityimpl{e, &fooimpl{e}, &barimpl{e}} x.sayfoo() x.saybar() x.sayfoobar() its output:
foo 3 bar 3 foobar 3 try variant on go playground.
foobarentity creation
note when creating foobarentityimpl, value of entity used in multiple composite literals. since created 1 entity (entityimpl) , used in places, there one id used in different implementation classes, "reference" passed each structs, not duplicate / copy. intended / required usage.
since foobarentityimpl creation non-trivial , error-prone, recommended create constructor-like function:
func newfoobarentity(id int) foobarentity { e := &entityimpl{id} return &foobarentityimpl{e, &fooimpl{e}, &barimpl{e}} } note factory function newfoobarentity() returns value of interface type , not implementation type (good practice followed).
it practice make implementation types un-exported, , export interfaces, implementation names entityimpl, fooimpl, barimpl, foobarentityimpl.
some related questions worth checking out
what idiomatic way in go create complex hierarchy of structs?
is possible call overridden method parent struct in golang?
Comments
Post a Comment