Elegant way to implement template method pattern in Golang

The essence of the template method pattern is it allows you to inject in an implementation of a particular function or functions into the skeleton of an algorithm.

You can achieve this in Go by injecting in a function or an interface into your Runner. To achieve the basic template method pattern you don't really need your Logger struct at all:

package main

import (
    "fmt"
)

type Runner struct {
    run func()
}

func (r *Runner) Start() {
    // some prepare stuff...
    r.run()
}

func runLog() {
    fmt.Println("Running")
}

func NewLogger() *Runner {
    return &Runner{runLog}
}

func main() {
    l := NewLogger()
    l.Start()
}

Logger embeds a pointer which will be nil when you allocate the struct. That's because embedding does not put everything inside the struct, it actually creates a field (named Runner of type *Runner in your case) and the language gives you some syntactic sugar to access what's inside it. In your case it means that you can access Runner fields in two ways:

l := Logger{}
l.needStop = false
//or
l.Runner.needStop = false

To fix the error you need to allocate Runner field inside the Logger like so:

l := Logger{Runner:&Runner{}}

Or embed by value instead of pointer.


The key to have the Template Method Design Pattern work in Golang is to properly use the embedding feature and the function assignment.

Below, a code snippet which works as expected.

package main

import (
    "fmt"
)

type Runner struct {
    run func()  // 1. this has to get assigned the actual implementation
}

func NewRunner(i func()) *Runner {
    return &Runner{i}
}

func (r *Runner) Start() {
    r.run()
}

type Logger struct {
    Runner
}

func NewLogger() *Logger {
    l := Logger{}
    l.run = l.loggerRun  // 2. the actual version is assigned
    return &l
}

func (l *Logger) loggerRun() {
    fmt.Println("Logger is running...")
}

func main() {
    l := NewLogger()  // 3. constructor should be used, to get the assignment working
    l.Start()
}

The type Runner defines a func() attribute which is supposed to receive the actual implementation, according to the specific subtype. Start() wraps call to run(), and once invoked on the right receiver (the base one) it is be able to run the right version of run(): this happens iff in the constructor (i.e. NewLogger()) the actual version of the method run() is assigned to the attribute run of the embedded type.

And, output is:

Logger is running...

Program exited.

Here the code can be run, and modified to test any other variant of this design pattern.