Template-Method-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 99 von 104.
Das Template Method Pattern definiert das Skelett eines Algorithmus in einer Basisklasse und ermöglicht es Unterklassen, bestimmte Schritte zu überschreiben, ohne die Gesamtstruktur des Algorithmus zu ändern. Im Gegensatz zu Strategy, das ganze Algorithmen austauscht, behält Template Method den Algorithmus bei, erlaubt aber die Anpassung einzelner Schritte.
Die Basisklasse implementiert die Template-Methode (den Algorithmus) und ruft abstrakte oder virtuelle Methoden auf, die Unterklassen bereitstellen müssen:
#include <iostream>
class DataProcessor {
public:
// Template-Methode - definiert das Algorithmus-Skelett
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // Muss implementiert werden
virtual void processData() = 0; // Muss implementiert werden
// Hook - optionales Überschreiben mit Standardverhalten
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};
int main() {
CSVProcessor csv;
csv.process(); // Verwendet Standard-saveResults
JSONProcessor json;
json.process(); // Verwendet benutzerdefiniertes saveResults
}Die Methode process() ist die Template-Methode – sie definiert die feste Abfolge von Schritten. Unterklassen implementieren loadData() und processData() unterschiedlich, aber die Reihenfolge ändert sich nie. Die Methode saveResults() ist ein „Hook“ – sie verfügt über eine Standardimplementierung, die Unterklassen optional überschreiben können.
Verwenden Sie die Template-Methode, wenn Sie einen Algorithmus mit unveränderlichen Schritten haben, aber Flexibilität bei bestimmten Operationen benötigen, oder wenn Sie Code-Duplizierung in ähnlichen Klassen vermeiden möchten.
Aufgabe
EinfachLassen Sie uns ein Report Generator-System unter Verwendung des Template Method Patterns erstellen. Sie werden ein Framework entwickeln, in dem verschiedene Arten von Berichten (wie Verkaufsberichte und Inventarberichte) demselben Erstellungsprozess folgen, aber jeder Berichtstyp spezifische Schritte anpasst. Dies ist ein perfektes Szenario für die Template-Methode – der gesamte Workflow bleibt fest, während die einzelnen Schritte variieren.
Sie werden Ihren Code in drei Dateien organisieren:
ReportGenerator.h: Definieren Sie die abstrakte Basisklasse mit der Template-Methode.Erstellen Sie eine Klasse
ReportGenerator, die das Skelett des Berichterstellungsalgorithmus definiert. Ihre Template-MethodegenerateReport()sollte diese Schritte in der folgenden Reihenfolge ausführen:gatherData()— rein virtuell, muss von Unterklassen implementiert werdenformatReport()— rein virtuell, muss von Unterklassen implementiert werdenaddHeader()— ein Hook mit Standardverhalten, der--- Report ---ausgibtprintReport()— rein virtuell, muss von Unterklassen implementiert werden
Die Methode
addHeader()dient als Hook, den Unterklassen optional überschreiben können, um den Header anzupassen.Reports.h: Implementieren Sie zwei konkrete Berichtsgeneratoren.Erstellen Sie eine Klasse
SalesReport, die:gatherData()gibtGathering sales data from databaseausformatReport()gibtFormatting sales figuresausprintReport()gibtSales Total: $[amount]aus, wobei amount an den Konstruktor übergeben wird
Erstellen Sie eine Klasse
InventoryReport, die:gatherData()gibtScanning inventory recordsausformatReport()gibtOrganizing inventory by categoryausaddHeader()überschreibt den Hook, um=== Inventory Report ===auszugebenprintReport()gibtItems in stock: [count]aus, wobei count an den Konstruktor übergeben wird
Beide Klassen sollten ihre jeweiligen Werte (amount oder count) über ihre Konstruktoren entgegennehmen.
main.cpp: Demonstrieren Sie das Template Method Pattern.Lesen Sie zwei Eingaben ein:
- Verkaufsbetrag (Integer)
- Inventarbestand (Integer)
Erstellen Sie einen
SalesReportmit dem Verkaufsbetrag und generieren Sie ihn. Erstellen Sie dann einenInventoryReportmit dem Inventarbestand und generieren Sie ihn. Geben Sie zur besseren Lesbarkeit eine Leerzeile zwischen den beiden Berichten aus.
Zum Beispiel mit den Eingaben 15000 und 250:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $15000
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 250Mit den Eingaben 8500 und 120:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $8500
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 120Beachten Sie, wie beide Berichte genau der gleichen Abfolge von Schritten folgen, die in generateReport() definiert ist, aber jeder diese Schritte unterschiedlich implementiert. Der InventoryReport demonstriert zudem das Überschreiben der Hook-Methode zur Anpassung des Headers, während SalesReport den Standard verwendet. Dies ist das Template Method Pattern in Aktion – die Algorithmusstruktur ist in der Basisklasse festgelegt, aber die Details sind flexibel.
Spickzettel
Das Template Method Pattern (Schablonenmethode) definiert das Skelett eines Algorithmus in einer Basisklasse und ermöglicht es Unterklassen, bestimmte Schritte zu überschreiben, ohne die Gesamtstruktur des Algorithmus zu ändern.
Die Basisklasse implementiert die Template-Methode (den Algorithmus) und ruft abstrakte oder virtuelle Methoden auf, die von den Unterklassen bereitgestellt werden müssen:
#include <iostream>
class DataProcessor {
public:
// Template-Methode - definiert das Skelett des Algorithmus
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // Muss implementiert werden
virtual void processData() = 0; // Muss implementiert werden
// Hook - optionales Überschreiben mit Standardverhalten
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};Die Template-Methode definiert die feste Abfolge von Schritten. Unterklassen implementieren abstrakte Methoden unterschiedlich, aber die Reihenfolge ändert sich nie. Hook-Methoden verfügen über Standardimplementierungen, die Unterklassen optional überschreiben können.
Verwenden Sie das Template Method Pattern, wenn Sie einen Algorithmus mit invarianten Schritten haben, aber Flexibilität bei bestimmten Operationen benötigen, oder wenn Sie Codeduplizierung über ähnliche Klassen hinweg vermeiden möchten.
Probier es selbst
#include <iostream>
#include "Reports.h"
using namespace std;
int main() {
// Eingaben lesen
int salesAmount;
int inventoryCount;
cin >> salesAmount;
cin >> inventoryCount;
// TODO: Erstelle einen SalesReport mit salesAmount und generiere ihn
// TODO: Drucke eine Leerzeile zwischen den Berichten
// TODO: Erstelle einen InventoryReport mit inventoryCount und generiere ihn
return 0;
}
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 OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins via CRTPPimpl-IdiomType ErasureEnum-Klassen & starke TypisierungException-Handling in OOPEigene Exception-Hierarchien14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container