Template-Method-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 94 von 107.
Das Template-Method-Muster definiert das Skelett eines Algorithmus in einem Basistyp und ermöglicht es Subtypen, 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, mittels 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-dataTemplate Method ist ideal, wenn Sie Algorithmen haben, die dieselbe Struktur teilen, sich aber in spezifischen Schritten unterscheiden, wie zum Beispiel Datenimport/-export-Pipelines, Berichtsgeneratoren oder Test-Frameworks.
Aufgabe
EinfachLassen Sie uns ein Berichtserstellungssystem mit dem Template Method Pattern erstellen! Sie werden ein Framework entwickeln, 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
ReportGeneratorInterface mit drei Methoden, die die Schritte der Berichtserstellung darstellen: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
ReportProcessorStruktur, die eineReportGeneratorImplementierung enthält. Fügen Sie eineGenerate() stringMethode hinzu, die die drei Schritte in der richtigen Reihenfolge ausführt: Sammeln, Formatieren und dann Ausgeben – und das Endergebnis zurückgibt.generators.go: Implementieren Sie konkrete Berichtsgeneratoren, die jeden Schritt anpassen.Erstellen Sie zwei Berichtstypen:
SalesReportmit einemRegionFeld (string)GatherData()gibt"sales-data-"+RegionzurückFormatData(data)gibt"SALES REPORT: "+datazurückOutputReport(formatted)gibt"Printed: "+formattedzurück
InventoryReportmit einemWarehouseFeld (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!
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 Lager)
scanner.Scan()
configValue := scanner.Text()
// TODO: Erstelle den passenden Generator basierend auf reportType
// - Wenn reportType "sales" ist, erstelle einen SalesReport, wobei Region auf configValue gesetzt wird
// - Wenn reportType "inventory" ist, erstelle einen InventoryReport, wobei Warehouse auf configValue gesetzt wird
// TODO: Erstelle einen ReportProcessor mit dem Generator
// TODO: Rufe Generate() auf und gib 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