有没有一种优雅的规范方法可以在 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;
}
在戈朗,我写了这样的东西:
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 结构)中的一些功能混合到基类例程中,就像基类状态可以从这个混合派生例程中获得一样。
模板方法模式的本质是它允许您将一个或多个特定函数的实现注入到算法的框架中。
您可以通过在 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()
}
Logger
嵌入一个指针,当你分配结构时,该指针将为nil。这是因为嵌入不会将所有内容都放在结构中,它实际上创建了一个字段(在您的情况下名为 *Runner
类型的Runner
),并且该语言为您提供了一些语法糖来访问其中的内容。在您的情况下,这意味着您可以通过两种方式访问Runner
字段:
l := Logger{}
l.needStop = false
//or
l.Runner.needStop = false
要修复错误,您需要在Logger
内分配Runner
字段,如下所示:
l := Logger{Runner:&Runner{}}
或者按值而不是指针嵌入。
让模板方法设计模式在 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.
在这里,可以运行和修改代码以测试此设计模式的任何其他变体。