How not to implement a decorator pattern in Go?
Lets assume you have the following interface in your go
codebase and
you want to implement very popular design pattern called decorator. It’s
a great pattern, that allows you to extend existing objects without
breaking down current API.
It’s very often my design pattern of choice, when I’m working with some legacy code.
type Doer interface {
Do(context.Context) (*DoerResult, error)
}
Probably, you also heard about interface embedding, so why not try it? What can go wrong?
type DoerDecorator struct {
Doer
}
After embedding our Doer
interface inside the decorator, wee need to
override its interface method. We can do it like below.
func (d *DoerDecorator) Do(ctx context.Context) (*DoerResult, error) {
doSomeThingElse()
return d.Do()
}
And it’s not a great idea at all. Also: compiler won’t complain about this. But we should understand further what’s happening here.
Our DoerDecorator
has got Doer
interface embedded in itself, so it doesn’t
need refer to specific field when calling Do
method. It can be handy very,
but right here it’s unforgivable mistake, because it leads to
infinite recursive function call.
Do
method will call doSomeThingElse
function and then call Do
method
which calls doSomeThingElse
and Do
method and again and again, forever.
To fix this up we have to replace return d.Do()
with return d.Doer.Do()
at
Do
method of DoerDecorator
. By doing so we’re telling compiler to call
Do
method of embedded interface instead of calling it’s own Do
method, which
leads us to previously mentioned recursive function call.
If you want to get rid of this and similar mistakes from your codebase, think about setting up staticcheck in your project’s CI. You will never catch all mistakes at code review (and it’s fine we’re only humans), so having additional tools that will seek for common coding mistakes is a great addition.
You can read more about infinite recursive call here.