Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to implement template method pattern in Golang

Is there an elegant canonical way to implement template method pattern in Go? In C++ this looks like this:

#include <iostream>
#include <memory>

class Runner {
public:
    void Start() {
        // some prepare stuff...
        Run();
    }
private:
    virtual void Run() = 0;
};

class Logger : public Runner {
private:
    virtual void Run() override {
        std::cout << "Running..." << std::endl;
    }
};

int main() {
    std::unique_ptr<Runner> l = std::make_unique<Logger>();
    l->Start();
    return 0;
}

In golang i wrote something like this:

package main

import (
    "fmt"
    "time"
)

type Runner struct {
    doRun func()
    needStop bool
}

func (r *Runner) Start() {
    go r.doRun()
}

func NewRunner(f func()) *Runner {
    return &Runner{f, false}
}

type Logger struct {
    *Runner
    i int
}

func NewLogger() *Logger {
    l := &Logger{}
    l.doRun = l.doRunImpl
    return l
}

func (l *Logger) doRunImpl() {
    time.Sleep(1 * time.Second)
    fmt.Println("Running")
}

func main() {
    l := NewLogger()
    l.Start()
    fmt.Println("Hello, playground")
}

But this code fails with runtime null pointer error. Basic idea is to mix in some functionality from derived classes (go structs) to the base class routine in a way that base class state is available from this mix-in derived routine.

like image 504
user6256186 Avatar asked Jun 04 '16 22:06

user6256186


3 Answers

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()
}
like image 135
Chris Drew Avatar answered Sep 22 '22 02:09

Chris Drew


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.

like image 34
creker Avatar answered Sep 18 '22 02:09

creker


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.

like image 36
Paolo Maresca Avatar answered Sep 18 '22 02:09

Paolo Maresca