Template-Method-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 94 von 107.
Das Template Method Pattern definiert das Skelett eines Algorithmus in einem Basistyp und ermöglicht es Untertypen, bestimmte Schritte zu überschreiben, ohne die Gesamtstruktur zu ändern. Während der Decorator Verhalten durch das Einpacken von Objekten hinzufügt, steuert die Template Method den Ablauf des Algorithmus und ermöglicht die Anpassung einzelner Schritte.
In Go implementieren wir dieses Muster, da es keine Vererbung gibt, mithilfe von Struct-Embedding in Kombination mit Interfaces. Das „Template“-Struct definiert die Algorithmusstruktur und ruft Methoden auf, die angepasst werden können:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}Konkrete Implementierungen stellen ihre eigenen Versionen für jeden Schritt bereit, während der Algorithmus-Ablauf unverändert bleibt:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }Die Template-Methode Execute orchestriert die Schritte in einer festen Reihenfolge:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Gespeichert: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Gespeichert: parsed-json-dataDie Template-Methode ist ideal, wenn Sie Algorithmen haben, die dieselbe Struktur teilen, sich aber in spezifischen Schritten unterscheiden, wie zum Beispiel Datenimport/-export-Pipelines, Berichtgeneratoren oder Test-Frameworks.
Aufgabe
EinfachLassen wir uns ein Berichtserstellungssystem mit dem Template Method Pattern bauen! Sie werden ein Framework erstellen, in dem verschiedene Berichtstypen demselben Erstellungsprozess folgen – Daten sammeln, formatieren und das Ergebnis ausgeben – aber jeder Berichtstyp diese Schritte unterschiedlich anpasst.
Sie werden Ihren Code auf drei Dateien verteilen:
report.go: Definieren Sie Ihr Interface und den Basis-Prozessor, der den Algorithmus zur Berichtserstellung orchestriert.Erstellen Sie ein
ReportGenerator-Interface mit drei Methoden, die die Schritte der Berichtserstellung repräsentieren:GatherData() string— ruft die Rohdaten für den Bericht abFormatData(data string) string— transformiert die Daten in das Format des BerichtsOutputReport(formatted string) string— erzeugt die endgültige Ausgabemeldung
Erstellen Sie eine
ReportProcessor-Struktur, die eineReportGenerator-Implementierung enthält. Fügen Sie eineGenerate() string-Methode hinzu, die die drei Schritte in der Reihenfolge ausführt: Sammeln, Formatieren, dann Ausgeben – und das Endergebnis zurückgibt.generators.go: Implementieren Sie konkrete Berichtsgeneratoren, die jeden Schritt anpassen.Erstellen Sie zwei Berichtstypen:
SalesReportmit einemRegion-Feld (string)GatherData()gibt"sales-data-"+RegionzurückFormatData(data)gibt"SALES REPORT: "+datazurückOutputReport(formatted)gibt"Printed: "+formattedzurück
InventoryReportmit einemWarehouse-Feld (string)GatherData()gibt"inventory-"+WarehousezurückFormatData(data)gibt"*** "+data+" ***"zurückOutputReport(formatted)gibt"Exported: "+formattedzurück
main.go: Demonstrieren Sie, wie dieselbe Algorithmusstruktur je nach Implementierung unterschiedliche Ergebnisse liefert.Lesen Sie den Berichtstyp (
"sales"oder"inventory") und den Konfigurationswert (Region für Sales, Lagerhaus für Inventory) ein. Erstellen Sie den entsprechenden Generator, betten Sie ihn in einenReportProcessorein, rufen SieGenerate()auf und geben Sie das Ergebnis aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Berichtstyp (
"sales"oder"inventory") - Zeile 2: Konfigurationswert (Regionsname oder Lagerhausname)
Zum Beispiel, gegeben:
sales
NorthSollte Ihre Ausgabe wie folgt aussehen:
Printed: SALES REPORT: sales-data-NorthUnd gegeben:
inventory
MainHubSollte Ihre Ausgabe wie folgt aussehen:
Exported: *** inventory-MainHub ***Und gegeben:
sales
WestSollte Ihre Ausgabe wie folgt aussehen:
Printed: SALES REPORT: sales-data-WestBeachten Sie, wie der ReportProcessor immer dieselben drei Schritte in derselben Reihenfolge aufruft, aber jeder Berichtstyp seine eigene Implementierung dieser Schritte bereitstellt. Das Skelett des Algorithmus bleibt fest, während die Details variieren – das ist das Template Method Pattern in Aktion!
Spickzettel
Das Template Method Pattern (Schablonenmethode) definiert das Skelett eines Algorithmus in einem Basistyp und ermöglicht es Untertypen, spezifische Schritte zu überschreiben, ohne die Gesamtstruktur zu ändern.
In Go implementieren Sie dieses Muster mithilfe von Struct-Embedding in Kombination mit Interfaces. Das „Template“-Struct definiert die Algorithmusstruktur und ruft Methoden auf, die angepasst werden können:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}Konkrete Implementierungen stellen ihre eigenen Versionen jedes Schritts bereit, während der Algorithmusablauf unverändert bleibt:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }Die Template-Methode orchestriert die Schritte in einer festen Reihenfolge:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Saved: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-dataDas Template Method Pattern ist ideal, wenn Algorithmen dieselbe Struktur teilen, sich aber in spezifischen Schritten unterscheiden, wie z. B. bei Datenimport/-export-Pipelines, Berichtgeneratoren oder Test-Frameworks.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Berichtstyp lesen
scanner.Scan()
reportType := scanner.Text()
// Konfigurationswert lesen (Region oder Lagerhaus)
scanner.Scan()
configValue := scanner.Text()
// TODO: Erstellen Sie den entsprechenden Generator basierend auf reportType
// - Wenn reportType "sales" ist, erstellen Sie einen SalesReport mit Region gesetzt auf configValue
// - Wenn reportType "inventory" ist, erstellen Sie einen InventoryReport mit Warehouse gesetzt auf configValue
// TODO: Erstellen Sie einen ReportProcessor mit dem Generator
// TODO: Rufen Sie Generate() auf und geben Sie das Ergebnis aus
_ = reportType
_ = configValue
fmt.Println("TODO: Generate and print the report")
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json mit Structshttp.Handler InterfaceZusammenfassung - REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool