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

Elegant way to implement template method pattern in Golang

有没有优雅规范的方式在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")
}

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

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

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

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

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

或按值而不是指针嵌入。

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

您可以通过在 Runner 中注入函数或接口来在 Go 中实现此目的。要实现基本模板方法模式,您根本不需要 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()
}

Template Method Design Pattern 在 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.

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