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

from abc import ABC, abstractmethod

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

class ConcreteClassA(AbstractClass):
    """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

from abc import ABC, abstractmethod
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

from abc import ABC, abstractmethod

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

class ConfigurableProcessor(ABC):
    """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

class MultiStepProcessor(ABC):
    """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 alsfinal(In Python kann dies durch Namenskonventionen erreicht werden)
  • Schrittmethoden sollten geschützt sein, um direkte externe Aufrufe zu vermeiden

3. Fehlerbehandlung

Beispiel

class RobustTemplate(ABC):
    """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 BasisklasseFileExporter
  • Implementieren Sie konkrete UnterklassenTXTExporter、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

  1. Code-Wiederverwendbarkeit verbessern: Gemeinsamen Code in die Elternklasse legen
  2. Erweiterbarkeit verbessern: Bestimmte Schritte durch Unterklassen erweitern
  3. Das Open/Closed-Prinzip einhalten: Offen für Erweiterungen, geschlossen für Änderungen
  4. 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
Weitere Erweiterungen