2016-06-04 10 views
3

Gibt es eine elegante kanonische Methode zum Implementieren eines Vorlagenmethodenmusters in Go? In C++ sieht das dies wie:Elegante Methode zum Implementieren eines Vorlagenmethodenmusters in 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") 
} 

Aber dieser Code nicht mit Laufzeit Null-Pointer-Fehler:

#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 ich so etwas wie dies schrieb. Grundidee ist es, einige Funktionen von abgeleiteten Klassen (go structs) mit der Basisklassenroutine so zu mischen, dass der Basisklassenstatus aus dieser abgeleiteten Routine verfügbar ist.

Antwort

3

Logger bettet einen Zeiger, der Null sein wird, wenn Sie die Struktur zuweisen. Das liegt daran, dass durch die Einbettung nicht alles in die Struktur eingefügt wird, sondern tatsächlich ein Feld (in Ihrem Fall Runner vom Typ *Runner) erstellt wird und die Sprache Ihnen syntaktischen Zucker liefert, um auf das zuzugreifen, was darin ist. In Ihrem Fall bedeutet dies, dass Sie Runner Felder auf zwei Arten zugreifen:

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

Oder einbetten anstelle von Wert:

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

, welchen Fehler Sie Runner Feld innerhalb der Logger wie so zuweisen müssen beheben von Zeiger.

+0

Ja, jetzt funktioniert es. Aber es sieht immer noch irgendwie hässlich aus ... Danke, jedenfalls – user6256186

2

Die Essenz des Vorlagenmethodenmusters ist es ermöglicht es Ihnen, in einer Implementierung einer bestimmten Funktion oder Funktionen in das Skelett eines Algorithmus zu injizieren.

Sie können dies in Go erreichen, indem Sie eine Funktion oder eine Schnittstelle in Ihre Runner einspeisen. Um die Grundschablonenmethode zu erreichen Sie brauchen gar nicht wirklich Ihre Logger Struktur:

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

In komplexeren Fällen benötigen wir Zugriff auf den Zustand der Logger && Runner-Instanz.In Ihrem Code können Sie dies nur erreichen, indem Sie das Logger-Objekt als Argument an die Funktion runLog() übergeben. Ich persönlich denke, dass es noch hässlicher ist als mein Beispiel. – user6256186

0

Der Schlüssel der Template Method Design Pattern Arbeit in Golang zu haben, ist richtig die Einbettung Funktion und die Funktionszuordnung verwenden .

Im Folgenden ein Code-Snippet, das wie erwartet funktioniert.

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

Der Typ Runner definiert ein func() Attribut, das die tatsächliche Implementierung empfangen sollte, je nach der spezifischen Subtyps. Start() Wraps Anruf an run(), und einmal auf dem richtigen Empfänger (der Basis) aufgerufen wird in der Lage sein, die richtige Version von run(): dies geschieht, wenn im Konstruktor (dh NewLogger()) die aktuelle Version der Methode run() zugeordnet ist das Attribut run des eingebetteten Typs.

Und Ausgabe lautet:

Logger is running... 

Program exited. 

Here der Code ausgeführt werden kann, und andere Variante dieses Design-Muster zu testen modifiziert.