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