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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With