Go is not (quite) an object oriented language: it does not have classes and it does not have type inheritance; but it supports a similar construct called embedding both on struct
level and on interface
level, and it does have methods.
So you should stop thinking in OOP and start thinking in composition. Since you said in your comments that FooLinkerEntity
will never be used on its own, that helps us achieve what you want in a clean way.
I will use new names and less functionality to concentrate on the problem and solution, which results in shorter code and which is also easier to understand.
The full code can be viewed and tested on the Go Playground.
Entity
The simple Entity
and its implementation will look like this:
type Entity interface {
Id() int
}
type EntityImpl struct{ id int }
func (e *EntityImpl) Id() int { return e.id }
Foo and Bar
In your example FooLinkerEntity
and BarLinkerEntity
are just decorators, so they don't need to embed (extend in OOP) Entity
, and their implementations don't need to embed EntityImpl
. However, since we want to use the Entity.Id()
method, we need an Entity
value, which may or may not be EntityImpl
, but let's not restrict their implementation. Also we may choose to embed it or make it a "regular" struct field, it 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
and Bar
:
f := FooImpl{&EntityImpl{1}}
f.SayFoo()
b := BarImpl{&EntityImpl{2}}
b.SayBar()
Output:
Foo 1
Bar 2
FooBarEntity
Now let's see a "real" entity which is an Entity
(implements Entity
) and has both the features provided by Foo
and 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 the FooBarEntityImpl
does not need to know (does not use) the internals of the Entity
, Foo
and Bar
implementations (EntityImpl
, FooImpl
and BarImpl
in our cases), we may choose to embed only the interfaces and not the implementations (but in this case we can't call x.FooImpl.Id()
because Foo
does not implement Entity
- that is an implementation detail which was our initial statement that we don't need / use it):
type FooBarEntityImpl struct {
Entity
Foo
Bar
}
func (x *FooBarEntityImpl) SayFooBar() { fmt.Println("FooBar", x.Id()) }
Its usage is the 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 this variant on the Go Playground.
FooBarEntity creation
Note that when creating FooBarEntityImpl
, a value of Entity
is to be used in multiple composite literals. Since we created only one Entity
(EntityImpl
) and we used this in all places, there is only one id used in different implementation classes, only a "reference" is passed to each structs, not a duplicate / copy. This is also the intended / required usage.
Since FooBarEntityImpl
creation is non-trivial and error-prone, it is recommended to create a constructor-like function:
func NewFooBarEntity(id int) FooBarEntity {
e := &EntityImpl{id}
return &FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
}
Note that the factory function NewFooBarEntity()
returns a value of interface type and not the implementation type (good practice to be followed).
It is also a good practice to make the implementation types un-exported, and only export the interfaces, so implementation names would be entityImpl
, fooImpl
, barImpl
, fooBarEntityImpl
.
Some related questions worth checking out
What is the idiomatic way in Go to create a complex hierarchy of structs?
is it possible to call overridden method from parent struct in golang?
Can embedded struct method have knowledge of parent/child?
Go embedded struct call child method instead parent method