Python-Schablonenmethodenmuster
Das Template-Method-Muster ist ein Verhaltens-Entwurfsmuster, das das Skelett eines Algorithmus in einer Operation definiert und einige Schritte an Unterklassen delegiert. Es ermöglicht Unterklassen, bestimmte Schritte eines Algorithmus neu zu definieren, ohne die Struktur des Algorithmus zu ändern.
Einfach ausgedrückt ist es wie ein Kochrezept: Das Rezept gibt die Reihenfolge der Schritte vor (Gemüse waschen, schneiden, anbraten, anrichten), aber wie genau gewaschen, geschnitten und angebraten wird, können verschiedene Köche nach ihrem eigenen Stil umsetzen.
Warum brauchen wir das Template-Method-Muster?
Code-Wiederverwendung
Das Template-Method-Muster verlagert invariantes Verhalten in die Oberklasse und vermeidet Code-Duplizierung in Unterklassen. Mehrere Unterklassen können die Algorithmusstruktur gemeinsam nutzen, die durch dieselbe Template-Methode definiert wird.
Erweiterbarkeit
Unterklassen können einen Teil des Verhaltens des Algorithmus erweitern oder ändern, indem sie Hook-Methoden oder konkrete Schrittmethoden überschreiben, ohne die Gesamtstruktur des Algorithmus zu ändern.
Inversion der Steuerung
Die Elternklasse steuert den Ausführungsfluss des Algorithmus, und Unterklassen müssen sich nur auf die konkreten Schritte konzentrieren, die sie implementieren müssen, wodurch das „Hollywood-Prinzip“ realisiert wird – „Ruf uns nicht an, wir rufen dich an.“
Struktur des Template-Method-Musters
Lassen Sie uns die Struktur des Template-Method-Musters anhand eines Klassendiagramms verstehen:

Beschreibung der Kernkomponenten
| Komponente | Verantwortlichkeit | Beschreibung |
|---|---|---|
| AbstractClass | Definiert das Algorithmusgerüst | Enthält die Template-Methode und abstrakte Schrittmethoden |
| ConcreteClass | Implementiert konkrete Schritte | Implementiert die von der Elternklasse definierten abstrakten Methoden |
| template_method | Template-Methode | Definiert die invarianten Teile des Algorithmus |
| step1, step2 | Abstrakte Methode | Spezifische Schritte, die von Unterklassen implementiert werden müssen |
| hook | Hook-Methode | Optionaler Schritt; Unterklassen können wählen, ob sie ihn überschreiben |
Grundlegende Syntax und Implementierung
Definition der abstrakten Basisklasse
Beispiel
class AbstractClass(ABC):
"""Abstrakte Basisklasse des Template-Methoden-Musters"""
def template_method(self):
"""Template-Methode - definiert das Algorithmus-Gerüst"""
self.step1()
self.step2()
self.hook()
self.step3()
@abstractmethod
def step1(self):
"""Abstrakte Methode 1 - muss von Unterklassen implementiert werden"""
pass
@abstractmethod
def step2(self):
"""Abstrakte Methode 2 - muss von Unterklassen implementiert werden"""
pass
def step3(self):
"""Konkrete Methode - hat eine Standardimplementierung"""
print("Schritt 3 ausführen - Standardimplementierung")
def hook(self):
"""Hook-Methode - optionaler Schritt; Unterklassen können sie bei Bedarf überschreiben"""
print("Hook-Methode ausführen - Standard tut nichts")
Implementierung konkreter Unterklassen
Beispiel
"""Konkrete Implementierungsklasse A"""
def step1(self):
print("ConcreteClassA - Schritt 1 wird ausgeführt")
def step2(self):
print("ConcreteClassA - Schritt 2 wird ausgeführt")
def hook(self):
print("ConcreteClassA - Hook-Methode wird überschrieben, um zusätzliche Funktionalität hinzuzufügen")
class ConcreteClassB(AbstractClass):
"""Konkrete Implementierungsklasse B"""
def step1(self):
print("ConcreteClassB - Schritt 1 wird ausgeführt")
def step2(self):
print("ConcreteClassB - Schritt 2 wird ausgeführt")
# Hook-Methode nicht überschreiben; Standardimplementierung verwenden
Praxisnahe Anwendungsbeispiele
Lassen Sie uns das Template-Method-Muster anhand mehrerer praktischer Beispiele besser verstehen.
Beispiel 1: Datenverarbeitungsvorlage
Beispiel
import json
import csv
class DataProcessor(ABC):
"""Datenverarbeitungsvorlage"""
def process_data(self, input_file, output_file):
"""Datenverarbeitungsvorlagenmethode"""
print(f"Datenverarbeitung starten: {input_file} -> {output_file}")
# Daten lesen
data = self.read_data(input_file)
print(f"Gelesen: {len(data)} Datensätze")
# Daten transformieren
transformed_data = self.transform_data(data)
print("Datentransformation abgeschlossen")
# Daten speichern
self.save_data(transformed_data, output_file)
print("Datenspeicherung abgeschlossen")
# Aufräumen
self.cleanup()
@abstractmethod
def read_data(self, file_path):
"""Daten lesen - abstrakte Methode"""
pass
@abstractmethod
def transform_data(self, data):
"""Daten transformieren - abstrakte Methode"""
pass
def save_data(self, data, file_path):
"""Daten speichern - konkrete Methode"""
with open(file_path, 'w', encoding='utf-8') as f:
if isinstance(data, list):
for item in data:
f.write(str(item) + '\n')
else:
f.write(str(data))
def cleanup(self):
"""Bereinigung - Hook-Methode"""
print("Bereinigung abgeschlossen")
class JSONProcessor(DataProcessor):
"""JSON-Datenprozessor"""
def read_data(self, file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def transform_data(self, data):
# Einfache Transformation: Alle String-Werte in Großbuchstaben umwandeln
if isinstance(data, dict):
return {k: v.upper() if isinstance(v, str) else v
for k, v in data.items()}
elif isinstance(data, list):
return [item.upper() if isinstance(item, str) else item
for item in data]
return data
class CSVProcessor(DataProcessor):
"""CSV-Datenprozessor"""
def read_data(self, file_path):
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def transform_data(self, data):
# Füge jedem Datensatz einen Verarbeitungszeitstempel hinzu
import datetime
timestamp = datetime.datetime.now().isoformat()
for record in data:
record['processed_at'] = timestamp
return data
def cleanup(self):
"""Überschreibe die cleanup-Methode, um zusätzliche Bereinigungslogik hinzuzufügen"""
print("Bereinigung des CSV-Prozessors abgeschlossen")
print("Ressourcen des CSV-Parsers freigeben")
# Verwendungsbeispiel
if __name__ == "__main__":
# Testdaten erstellen
import os
# JSON-Prozessor testen
json_data = {'name': 'john', 'age': 30, 'city': 'new york'}
with open('test.json', 'w') as f:
json.dump(json_data, f)
json_processor = JSONProcessor()
json_processor.process_data('test.json', 'output_json.txt')
print("\n" + "="*50 + "\n")
# CSV-Prozessor testen
csv_data = "name,age,city\njohn,30,new york\njane,25,los angeles"
with open('test.csv', 'w') as f:
f.write(csv_data)
csv_processor = CSVProcessor()
csv_processor.process_data('test.csv', 'output_csv.txt')
# Testdateien aufräumen
for file in ['test.json', 'test.csv', 'output_json.txt', 'output_csv.txt']:
if os.path.exists(file):
os.remove(file)
Beispiel 2: Vorlage zur Getränkeherstellung
Beispiel
class BeverageMaker(ABC):
"""Vorlage zur Getränkezubereitung"""
def make_beverage(self):
"""Schablonenmethode zur Getränkezubereitung"""
self.boil_water()
self.brew()
self.pour_in_cup()
if self.customer_wants_condiments():
self.add_condiments()
self.serve()
def boil_water(self):
"""Wasser kochen - konkrete Methode"""
print("Wasser kochen")
@abstractmethod
def brew(self):
"""Brew - abstrakte Methode"""
pass
def pour_in_cup(self):
"""In die Tasse gießen - konkrete Methode"""
print("In die Tasse gießen")
@abstractmethod
def add_condiments(self):
"""Zusätze hinzufügen - abstrakte Methode"""
pass
def customer_wants_condiments(self):
"""Hook-Methode - ob der Kunde Zusätze möchte"""
return True
def serve(self):
"""Getränk servieren - konkrete Methode"""
print("Ihr Getränk ist fertig, genießen Sie es!")
class CoffeeMaker(BeverageMaker):
"""Kaffee zubereiten"""
def brew(self):
print("Kaffeesatz mit kochendem Wasser aufbrühen")
def add_condiments(self):
print("Zucker und Milch hinzufügen")
def customer_wants_condiments(self):
answer = input("Möchten Sie Zucker und Milch in Ihrem Kaffee? (j/n): ")
return answer.lower() == 'y'
class TeaMaker(BeverageMaker):
"""Tee zubereiten"""
def brew(self):
print("Teeblätter in kochendem Wasser ziehen lassen")
def add_condiments(self):
print("Zitrone hinzufügen")
# Verwendungsbeispiel
if __name__ == "__main__":
print("Kaffee zubereiten:")
coffee = CoffeeMaker()
coffee.make_beverage()
print("\n" + "="*30 + "\n")
print("Tee zubereiten:")
tea = TeaMaker()
tea.make_beverage()
Varianten des Template-Method-Musters
1. Template-Methode mit Parametern
Beispiel
"""Konfigurierbarer Datenprozessor"""
def process_with_config(self, input_file, output_file, config):
"""Template-Methode mit Konfigurationsparametern"""
self.validate_config(config)
data = self.read_data(input_file)
processed_data = self.process_with_config_impl(data, config)
self.save_data(processed_data, output_file)
self.post_process(config)
def validate_config(self, config):
"""Konfiguration validieren - konkrete Methode"""
required_keys = ['format', 'encoding']
for key in required_keys:
if key not in config:
raise ValueError(f"Erforderlicher Schlüssel fehlt in der Konfiguration: {key}")
@abstractmethod
def process_with_config_impl(self, data, config):
"""Implementierung zur Verarbeitung von Daten mithilfe der Konfiguration"""
pass
def post_process(self, config):
"""Nachbearbeitung - Hook-Methode"""
if config.get('cleanup', False):
print("Bereinigungsvorgänge werden ausgeführt")
2. Mehrstufige Template-Methode
Beispiel
"""Mehrstufiger Prozessor"""
def complex_processing(self):
"""Komplexer Verarbeitungsablauf"""
self.initialize()
self.pre_process()
self.main_process()
self.post_process()
self.finalize()
def initialize(self):
print("Prozessor wird initialisiert")
def pre_process(self):
"""Vorverarbeitung - Hook-Methode"""
pass
@abstractmethod
def main_process(self):
"""Hauptverarbeitungslogik"""
pass
def post_process(self):
"""Nachbearbeitung - Hook-Methode"""
pass
def finalize(self):
print("Verarbeitung abgeschlossen")
Best Practices und Überlegungen
1. Abstrakte Methoden angemessen verwenden
- Nur Methoden, die wirklich von Unterklassen implementiert werden müssen, als abstrakte Methoden deklarieren
- Hook-Methoden mit Standardimplementierungen für optionale Schritte bereitstellen
2. Zugriffskontrolle von Template-Methoden
- Template-Methoden sollten normalerweise deklariert werden als
final(In Python kann dies durch Namenskonventionen erreicht werden) - Schrittmethoden sollten geschützt sein, um direkte externe Aufrufe zu vermeiden
3. Fehlerbehandlung
Beispiel
"""Robuste Template-Klasse"""
def template_method(self):
try:
self.setup()
self.execute_steps()
except Exception as e:
self.handle_error(e)
finally:
self.cleanup()
def execute_steps(self):
"""Abfolge der Ausführungsschritte"""
self.step1()
self.step2()
self.step3()
def handle_error(self, error):
"""Fehlerbehandlung - Hook-Methode"""
print(f"Während der Verarbeitung ist ein Fehler aufgetreten: {error}")
@abstractmethod
def setup(self):
pass
@abstractmethod
def step1(self):
pass
@abstractmethod
def step2(self):
pass
@abstractmethod
def step3(self):
pass
def cleanup(self):
print("Ressourcen aufräumen")
Praktische Übungen
Jetzt sind Sie an der Reihe zu üben! Vervollständigen Sie die folgenden Übungen, um Ihr Verständnis des Template-Method-Musters zu festigen:
Übung 1: Implementieren Sie eine Dateiexport-Vorlage
Erstellen Sie eine Dateiexport-Vorlage, die den Export in verschiedene Formate (TXT, HTML, JSON) unterstützt.
Requirements:
- Definieren Sie eine abstrakte Basisklasse
FileExporter - Implementieren Sie konkrete Unterklassen
TXTExporter、HTMLExporter、JSONExporter - Die Template-Methode sollte Folgendes umfassen: Datenvorbereitung, Datenformatierung, Dateispeicherung, Nachbearbeitung und weitere Schritte.
Übung 2: Verbessern Sie die Vorlage zur Getränkezubereitung
Erweitern Sie die vorherige Getränkezubereitungs-Vorlage und fügen Sie die folgenden Funktionen hinzu:
- Unterstützung bei der Auswahl der Getränkegröße (klein, mittel, groß)
- Funktionalität zur Preisberechnung hinzufügen
- Unterstützung für benutzerdefinierte Beilagen
Zusammenfassung
Das Template-Method-Muster ist ein leistungsstarkes und praktisches Entwurfsmuster, das uns auf folgende Weise hilft, besseren Code zu schreiben:
Hauptvorteile
- Code-Wiederverwendbarkeit verbessern: Gemeinsamen Code in die Elternklasse legen
- Erweiterbarkeit verbessern: Bestimmte Schritte durch Unterklassen erweitern
- Das Open/Closed-Prinzip einhalten: Offen für Erweiterungen, geschlossen für Änderungen
- Umkehrung der Steuerung: Die Elternklasse steuert den Ablauf, und Unterklassen implementieren die Details
Geeignete Szenarien
- Mehrere Klassen haben dieselbe Methode, aber die konkreten Implementierungen unterscheiden sich
- Erweiterungspunkte von Unterklassen müssen kontrolliert werden
- Es muss ein Algorithmusgerüst definiert werden, aber bestimmte Schritte dürfen variieren
Kernpunkte
- Die Template-Methode definiert die invarianten Teile des Algorithmus
- Abstrakte Methoden definieren die variablen Teile des Algorithmus
- Hook-Methoden bieten optionale Erweiterungspunkte