2

I'm trying to clean up my code base by doing a better job defining interfaces and using embedded structs to reuse functionality. In my case I have many entity types that can be linked to various objects. I want to define interfaces that capture the requirements and structs that implement the interfaces which can then be embedded into the entities.

// All entities implement this interface
type Entity interface {
  Identifier()
  Type()
}

// Interface for entities that can link Foos
type FooLinker interface {
  LinkFoo()
}

type FooLinkerEntity struct {
  Foo []*Foo
}

func (f *FooLinkerEntity) LinkFoo() {
  // Issue: Need to access Identifier() and Type() here
  // but FooLinkerEntity doesn't implement Entity
}

// Interface for entities that can link Bars
type BarLinker interface {
  LinkBar()
}

type BarLinkerEntity struct {
  Bar []*Bar
}

func (b *BarLinkerEntity) LinkBar() {
  // Issues: Need to access Identifier() and Type() here
  // but BarLinkerEntity doesn't implement Entity
}

So my first thought was to have FooLinkerEntity and BarLinkerEntity just implement the 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, this ends up with an ambiguity error for any types that can link both Foos and Bars.

// Baz.Identifier() is ambiguous between EntityModel, FooLinkerEntity,
// and BarLinkerEntity.
type Baz struct {
    EntityModel
    FooLinkerEntity
    BarLinkerEntity
}

What's the correct Go way to structure this type of code? Do I just do a type assertion in LinkFoo() and LinkBar() to get to Identifier() and Type()? Is there any way to get this check at compile time instead of runtime?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Bill
  • 25,119
  • 8
  • 94
  • 125
  • why `FooLinkerEntity doesn't implement Entity`? seems `FooLinkerEntity` is a subtype of `Entity` – Jiang YD Apr 19 '16 at 07:01
  • Enitty is an interface which FooLinkerEntity doesn't directly implement (or if it does, I end up with the ambiguity error). – Bill Apr 19 '16 at 07:09
  • I know it not implement now, but I mean, as you said `All entities implement this(Entity ) interface`, why not make it implement `Entity` interface? or could you post a complete code to make thinning clearer. – Jiang YD Apr 19 '16 at 07:12
  • That's the whole problem. Take a look at the Baz struct above. If I have FooLinkerEntity and BarLinkerEntity implement the Entity interface I can no longer embed them into other entities without creating an ambiguity issue. FooLinkerEntity will never be used on its own, I'm just using it to encapsulate functionality that can be embedded in other entities. – Bill Apr 19 '16 at 07:15

2 Answers2

4

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

icza
  • 389,944
  • 63
  • 907
  • 827
  • This is the ugly part `x := FooBarEntityImpl{e, FooImpl{e}, BarImpl{e}}` that I'm trying to get around. I only want FooBarEntityImpl to have one id, not three different ones that all need to be initialized. Composition doesn't seem to work if the combosable parts depend on shared properties. – Bill Apr 19 '16 at 07:52
  • @Bill The ugly part isn't that ugly: `FooBarEntityImpl` only has **one** id which is in `e`. Initialization just passes the **same** `e` to all the implementations that need / use an `Entity`: _reference_, not duplication / copy. And this you cannot avoid as there is no inheritance. – icza Apr 19 '16 at 07:55
  • It unfortunately makes it so that you need to know the internals of FooBarEntityImpl in order to do serialization since you need to initialize the compositions. I really just want access to the parent entity which isn't possible.So I guess I need to do a hundred cut and pastes so that each entity implements SayFoo and SayBar directly instead of trying to use composition. This way they will have access to Id() since the parent element correctly implements it. – Bill Apr 19 '16 at 08:08
  • @Bill Please see edited answer about `FooBarEntity` creation. Also: _"I ... want to access to the parent entity"_ - there is no inheritance (and therefore no parent) in Go, try to think differently. – icza Apr 19 '16 at 08:12
  • @Bill Another way to avoid all this inheritance and composition, is to have a function that takes any number of parameters in this case the `Entity` interfaces and returns a serialized string by calling respective interface methods. This avoids the relationships and also gives a little more clarity. If the pure intention is serializing, composition and inheritance relationships seems to be an overkill (regardless whether its Go or something like Java with inheritance). – Will C Apr 20 '16 at 00:21
  • Wow you helped me understand how it works... It's really unclear for a Go beginner to know what's going on if you don't know how to use it. – xdevs23 Jan 28 '18 at 19:44
0

Seems to me having three ID in one structure with methods relying on them is even semantically incorrect. To not be ambiguous you should write some more code to my mind. For example something like this

type Baz struct {
    EntityModel
    Foo []*Foo
    Bar []*Bar
}
func (b Baz) LinkFoo() {
    (&FooLinkerEntity{b.EntityModel, b.Foo}).LinkFoo()
}
func (b Baz) LinkBar() {
    (&BarLinkerEntity{b.EntityModel, b.Bar}).LinkBar()
}
Uvelichitel
  • 8,220
  • 1
  • 19
  • 36