6

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

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

4

3 回答 3

7

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

你可以在 Go 中通过将函数或接口注入到你的Runner. 要实现基本的模板方法模式,您根本不需要您的Logger结构:

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()
}
于 2016-06-06T13:32:13.423 回答
5

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

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

要修复错误,您需要在Runner内部分配字段,Logger如下所示:

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

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

于 2016-06-04T23:35:55.700 回答
3

模板方法设计模式在 Golang 中发挥作用的关键是正确使用嵌入特性函数赋值

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

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():如果在构造函数中发生这种情况(即NewLogger())方法的实际版本run()被分配给run嵌入的属性类型。

而且,输出是:

Logger is running...

Program exited.

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

于 2016-10-16T16:52:28.350 回答