在Golang中实现模板方法模式的优雅方式

时间:2016-06-04 22:48:18

标签: c++ go template-method-pattern

在Go中实现模板方法模式是否有优雅的规范方法? 在C ++中,它看起来像这样:

#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;
}

在golang中我写了这样的东西:

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")
}

但此代码因运行时空指针错误而失败。 基本思想是将派生类(转换结构)中的某些功能混合到基类例程中,使得基本类状态可从此混合派生例程中获得。

3 个答案:

答案 0 :(得分:4)

Logger嵌入一个指针,当你分配结构时它将是nil。这是因为嵌入不会将所有内容放在结构中,它实际上会创建一个字段(在您的情况下名为Runner,类型为*Runner)并且该语言为您提供了一些语法糖来访问其中的内容。在您的情况下,这意味着您可以通过两种方式访问​​Runner字段:

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

要解决错误,您需要在Runner内分配Logger字段,如下所示:

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

或者按值而不是指针嵌入。

答案 1 :(得分:4)

模板方法模式的本质是它允许您将特定函数或函数的实现注入到算法的框架中。

您可以通过在IRepository中注入函数或界面来在Go中实现此目的。要实现基本的模板方法模式,您根本不需要Runner结构:

Logger

答案 2 :(得分:3)

在Golang中使Template Method Design Pattern工作的关键是正确使用嵌入功能功能分配

下面是一段按预期工作的代码段。

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()
}

Runner类型根据特定的子类型定义了一个func()属性,该属性应该接收实际的实现。 Start()包含对run()的调用,一旦在右边的接收者(基本的)上调用,它就能够运行正确版本的run():这在构造函数中发生iff(即NewLogger())方法run()的实际版本已分配给嵌入类型的属性run

而且,输出是:

Logger is running...

Program exited.

Here代码可以运行,并进行修改以测试此设计模式的任何其他变体。