Python-Kommandomuster

Das Befehlsmuster ist ein Verhaltens-Entwurfsmuster, das eine Anfrage als Objekt kapselt und es dadurch ermöglicht, Clients mit unterschiedlichen Anfragen zu parametrisieren. Einfach ausgedrückt,verpackt das Befehlsmuster Operationen (wie das Öffnen von Dateien, das Speichern von Daten) in unabhängige Objekte,wodurch Sie Operationen auf dieselbe Weise behandeln können, wie Sie Daten behandeln.

Analogie im Alltag

Stellen Sie sich den Bestellvorgang in einem Restaurant vor:

  • Kunde(Client) muss nicht wissen, wie der Koch das Gericht zubereitet
  • Kellner(Invoker) nimmt die Bestellung entgegen, kocht aber nicht selbst
  • Bestellung(Command-Objekt) enthält die spezifischen Gerichtsinformationen
  • Koch(Receiver) führt die eigentlichen Kochvorgänge basierend auf der Bestellung aus

Dieses gut aufgeteilte Muster ist eine reale Erscheinungsform des Command-Patterns.


Warum braucht man das Command-Pattern?

Einschränkungen des traditionellen Ansatzes

In der traditionellen Programmierung rufen wir normalerweise direkt die Methoden eines Objekts auf:

Beispiel

class Light:
    def turn_on(self):
        print("Das Licht ist an")
   
    def turn_off(self):
        print("Das Licht ist aus")

# Direkter Aufruf
light = Light()
light.turn_on()
light.turn_off()

Dieser Ansatz hat mehrere Probleme:

  1. Enge Kopplung: Der Aufrufer muss die spezifische Schnittstelle des Empfängers kennen
  2. Schwer erweiterbar: Das Hinzufügen neuer Funktionen erfordert die Änderung vorhandenen Codes
  3. Keine Unterstützung für Undo/Redo: Operationen können nach der Ausführung nicht rückgängig gemacht werden

Vorteile des Command-Musters

Das Command-Muster löst die oben genannten Probleme durch die Einführung einer Zwischenschicht (Befehlsobjekt) und bietet:

  • Entkopplung: Keine direkte Abhängigkeit zwischen Aufrufer und Empfänger
  • Erweiterbarkeit: Einfaches Hinzufügen neuer Befehle
  • Unterstützung für Undo/Redo: Der Operationsverlauf kann aufgezeichnet werden
  • Warteschlangenunterstützung: Befehle können für eine verzögerte Ausführung in eine Warteschlange gestellt werden

Kernkomponenten des Befehlsmusters

Grundstruktur

Das Befehlsmuster umfasst vier Kernrollen:

Rolle Verantwortlichkeit Beispiel
Command(Befehlsschnittstelle) Deklariert die Schnittstelle zum Ausführen von Operationen execute(), undo()
ConcreteCommand(Konkreter Befehl) Implementiert die Befehlsschnittstelle und bindet den Empfänger LightOnCommand, LightOffCommand
Receiver(Empfänger) Weiß, wie Operationen ausgeführt werden Light, TV
Invoker(Aufrufer) Speichert und führt Befehle aus RemoteControl

Vollständiges Codebeispiel

Grundlegende Implementierung

Lass uns das Befehlsmuster anhand eines Beispiels zur Beleuchtungssteuerung im Smart Home verstehen:

Beispiel

from abc import ABC, abstractmethod
from typing import List

# 1. Befehlsschnittstelle
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
   
    @abstractmethod
    def undo(self):
        pass

# 2. Empfänger - Lichtgerät
class Light:
    def turn_on(self):
        print("💡 Licht ist an")
   
    def turn_off(self):
        print("💡 Licht ist aus")

# 3. Konkreter Befehl - Befehl zum Einschalten des Lichts
class LightOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_on()
   
    def undo(self):
        self.light.turn_off()

# 4. Konkretes Kommando - Befehl zum Ausschalten des Lichts
class LightOffCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_off()
   
    def undo(self):
        self.light.turn_on()

# 5. Aufrufer - Fernbedienung
class RemoteControl:
    def __init__(self):
        self.command = None
        self.history: List[Command] = []
   
    def set_command(self, command: Command):
        self.command = command
   
    def press_button(self):
        if self.command:
            self.command.execute()
            self.history.append(self.command)
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Client-Code
if __name__ == "__main__":
    # Gerät erstellen
    living_room_light = Light()
   
    # Befehle erstellen
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
   
    # Fernbedienung erstellen
    remote = RemoteControl()
   
    # Test: Licht einschalten
    print("=== Test: Licht einschalten ===")
    remote.set_command(light_on)
    remote.press_button()
   
    # Test: Licht ausschalten
    print("\n=== Test: Licht ausschalten ===")
    remote.set_command(light_off)
    remote.press_button()
   
    # Test: Rückgängig machen
    print("\n=== Test: Rückgängig-Operation ===")
    remote.press_undo()  # Ausschalten des Lichts rückgängig machen, sollte Licht einschalten
    remote.press_undo()  # Einschalten des Lichts rückgängig machen, sollte Licht ausschalten

Ausführungsergebnis:


Fortgeschrittenes Anwendungsbeispiel

Universelle Fernbedienung mit Unterstützung für mehrere Geräte

Lass uns das vorherige Beispiel erweitern und eine universelle Fernbedienung erstellen, die mehrere Geräte unterstützt:

Beispiel

# Neues Gerät - TV
class TV:
    def __init__(self, location: str):
        self.location = location
        self.is_on = False
        self.volume = 50
   
    def turn_on(self):
        self.is_on = True
        print(f"📺 {self.location} TV ist eingeschaltet")
   
    def turn_off(self):
        self.is_on = False
        print(f"📺 {self.location} TV ist ausgeschaltet")
   
    def set_volume(self, volume: int):
        self.volume = volume
        print(f"📺 {self.location} TV-Lautstärke eingestellt auf {volume}")

# TV-bezogene Befehle
class TVOnCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.turn_on()
   
    def undo(self):
        self.tv.turn_off()
        self.tv.volume = self.previous_volume

class TVVolumeUpCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.set_volume(min(100, self.tv.volume + 10))
   
    def undo(self):
        self.tv.set_volume(self.previous_volume)

# Makrobefehl - mehrere Befehle mit einem Tastendruck ausführen
class MacroCommand(Command):
    def __init__(self, commands: List[Command]):
        self.commands = commands
   
    def execute(self):
        for command in self.commands:
            command.execute()
   
    def undo(self):
        # Rückgängig in umgekehrter Reihenfolge
        for command in reversed(self.commands):
            command.undo()

# Verbesserte Fernbedienung
class AdvancedRemoteControl:
    def __init__(self, slot_count: int = 4):
        self.on_commands: List[Command] = [None] * slot_count
        self.off_commands: List[Command] = [None] * slot_count
        self.history: List[Command] = []
   
    def set_command(self, slot: int, on_command: Command, off_command: Command):
        self.on_commands[slot] = on_command
        self.off_commands[slot] = off_command
   
    def press_on_button(self, slot: int):
        if self.on_commands[slot]:
            self.on_commands[slot].execute()
            self.history.append(self.on_commands[slot])
   
    def press_off_button(self, slot: int):
        if self.off_commands[slot]:
            self.off_commands[slot].execute()
            self.history.append(self.off_commands[slot])
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Test der erweiterten Fernbedienung
def test_advanced_remote():
    print("=== Test der universellen Fernbedienung ===")
   
    # Geräte erstellen
    living_room_light = Light()
    bedroom_tv = TV("Schlafzimmer")
   
    # Befehle erstellen
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
    tv_on = TVOnCommand(bedroom_tv)
    tv_volume_up = TVVolumeUpCommand(bedroom_tv)
   
    # Makrobefehl erstellen - Kinomodus
    cinema_mode = MacroCommand([light_off, tv_on, tv_volume_up])
   
    # Fernbedienung einrichten
    remote = AdvancedRemoteControl()
    remote.set_command(0, light_on, light_off)      # Slot 0: Lichtsteuerung
    remote.set_command(1, tv_on, TVOnCommand(bedroom_tv))  # Slot 1: TV-Steuerung
    remote.set_command(2, cinema_mode, light_on)    # Slot 2: Kinomodus
   
    # Verschiedene Funktionen testen
    print("\n1. Licht einschalten:")
    remote.press_on_button(0)
   
    print("\n2. Kinomodus aktivieren:")
    remote.press_on_button(2)
   
    print("\n3. Operation rückgängig machen:")
    remote.press_undo()

if __name__ == "__main__":
    test_advanced_remote()

Parameter und Optionen des Command-Musters

Erweiterung der Command-Schnittstelle

In praktischen Anwendungen kann die Command-Schnittstelle bei Bedarf um weitere Funktionalität erweitert werden:

Beispiel

class AdvancedCommand(ABC):
    @abstractmethod
    def execute(self):
        """Befehl ausführen"""
        pass
   
    @abstractmethod
    def undo(self):
        """Befehl rückgängig machen"""
        pass
   
    @abstractmethod
    def redo(self):
        """Redo-Befehl"""
        pass
   
    @abstractmethod
    def can_execute(self) -> bool:
        """Prüfen, ob der Befehl ausgeführt werden kann"""
        pass
   
    @abstractmethod
    def get_description(self) -> str:
        """Befehlsbeschreibung abrufen"""
        pass

Befehlswarteschlange und verzögerte Ausführung

Das Command-Muster unterstützt auf natürliche Weise Befehlswarteschlangen, was in vielen Szenarien sehr nützlich ist:

Beispiel

class CommandQueue:
    def __init__(self):
        self.queue: List[Command] = []
   
    def add_command(self, command: Command):
        self.queue.append(command)
   
    def process_commands(self):
        while self.queue:
            command = self.queue.pop(0)
            if command.can_execute():
                command.execute()
   
    def clear(self):
        self.queue.clear()

Praktische Übungen

Übung 1: Implementieren Sie ein Klimaanlagen-Steuerungssystem

Versuchen Sie, das Command-Muster für ein Klimaanlagen-Steuerungssystem zu implementieren:

Beispiel

# Ihr Code hier
class AirConditioner:
    def __init__(self):
        self.temperature = 26
        self.is_on = False
   
    def turn_on(self):
        # Einschalten der Klimaanlage implementieren
        pass
   
    def turn_off(self):
        # Ausschalten der Klimaanlage implementieren
        pass
   
    def set_temperature(self, temp: int):
        # Einstellen der Temperatur implementieren
        pass

# Befehlsklassen für die Klimaanlage implementieren
# AirConditionerOnCommand
# AirConditionerOffCommand  
# TemperatureUpCommand
# TemperatureDownCommand

Übung 2: Undo-Funktionalität für einen Texteditor implementieren

Erstelle einen einfachen Texteditor, der Texteingabe und Undo-Operationen unterstützt:

Beispiel

class TextEditor:
    def __init__(self):
        self.content = ""
   
    def add_text(self, text: str):
        # Hinzufügen von Text implementieren
        pass
   
    def delete_text(self, length: int):
        # Löschen von Text implementieren
        pass

# Textbearbeitungsbefehle implementieren
# AddTextCommand
# DeleteTextCommand

FAQ

F: Für welche Szenarien eignet sich das Befehlsmuster?

A:Das Befehlsmuster eignet sich besonders für die folgenden Szenarien:

  • Wenn Operationen parametrisiert werden müssen (z. B. Schaltflächen, die an verschiedene Funktionen gebunden sind)
  • Wenn Rückgängig-/Wiederholen-Funktionalität benötigt wird
  • Wenn Operationen für eine verzögerte Ausführung in eine Warteschlange gestellt werden müssen
  • Wenn Operationsprotokolle aufgezeichnet werden müssen
  • Wenn Transaktionsoperationen unterstützt werden müssen

F: Was sind die Nachteile des Befehlsmusters?

A:Die wichtigsten Nachteile sind:

  • Es kann eine große Anzahl von Befehlsklassen entstehen
  • Es erhöht die Codekomplexität
  • Für einfache Operationen kann es zu umständlich sein

F: Wie entscheidet man, ob das Befehlsmuster verwendet werden soll?

A:Berücksichtigen Sie die folgenden Punkte:

  • Wenn Sie nur einfache Methodenaufrufe benötigen, verwenden Sie das Befehlsmuster nicht
  • Wenn Rückgängig-/Wiederholen-Funktionalität benötigt wird, wird es dringend empfohlen
  • Wenn das System eine hohe Entkopplung erfordert, ist das Befehlsmuster eine gute Wahl
  • Wenn Operationen in eine Warteschlange gestellt oder protokolliert werden müssen, ist das Befehlsmuster sehr gut geeignet

Zusammenfassung

Das Befehlsmuster entkoppelt den Aufrufer vom Empfänger, indem Operationen als Objekte gekapselt werden, und bietet leistungsstarke Erweiterbarkeit. Obwohl es die Codekomplexität etwas erhöht, ist das Befehlsmuster in Szenarien wie Rückgängig/Wiederholen, Operationswarteschlangen und Protokollierung ein unverzichtbares Entwurfsmuster.

Wichtige Punkte:

  • Das Command-Muster trennt "was zu tun ist" von "wer es tut"
  • Unterstützt erweiterte Funktionen wie Rückgängig, Wiederholen und Warteschlangen
  • Einfach mit neuen Befehlen erweiterbar
  • Weit verbreitet in GUI-Anwendungen, Transaktionssystemen und der Spieleentwicklung
Weitere Erweiterungen