Kommando-Entwurfsmuster

Kommando ist ein weiteres Entwurfsmuster, das durch die Verwendung von Funktionen, die als Argumente übergeben werden, vereinfacht werden kann:

title UML-Klassendiagramm für das Kommando-Entwurfsmuster

together {
    abstract class  Caller
    abstract class  Command {
        {method}    execute()
    }
}

together {
    abstract class  Client
    abstract class  Receiver {
        {method}    action()
    }
    class           ConcreteCommand {
        state
        {method}    execute()
    }
}

Caller *-> Command
Client -> Receiver
Client -> ConcreteCommand
Receiver <- ConcreteCommand
ConcreteCommand -u-|> Command

Das Ziel des Kommando-Entwurfsmusters ist es, ein Objekt, das eine Operation aufruft vom Receiver-Objekt zu entkoppeln, das die Operation implementiert. Im Beispiel aus dem Entwurfsmuster-Buch ist jedes Caller-Objekt ein Menüpunkt in einer grafischen Anwendung, und die Receiver-Objekte sind das zu bearbeitende Dokument oder die Anwendung selbst. Hierzu wird ein Command-Objekt zwischen die beiden gesetzt, das eine Schnittstelle mit einer einzigen Methode implementiert, die eine Methode im Receiver aufruft, um die gewünschte Operation durchzuführen. Auf diese Weise muss das Caller-Objekt das Interface des Receiver nicht kennen, und verschiedene Empfänger können durch verschiedene Command-Unterklassen angepasst werden. Caller wird mit einem ConcreteCommand-Befehl konfiguriert und ruft dessen execute()-Methode auf, um ihn auszuführen.

Command sind ein objektorientierter Ersatz für Callbacks. [1]

Die Frage ist nun, ob wir in Python wirklich einen solchen objektorientierten Ersatz für Callbacks brauchen? Können wir stattdessen nicht dem Caller einfach eine Funktion geben? Anstatt also Command.execute() aufzurufen, könnte der Caller einfach command() aufrufen. Dabei kann Command eine Klasse sein, die __call__() implementiert und Instanzen von Command wären dann sog. callables, die jeweils eine Liste von Funktionen für zukünftige Aufrufe enthalten, z.B.:

1class MacroCommand:
2    """A command that executes a list of command."""
3
4    def __init__(self, commands):
5        self.commands = list(commands)
6
7    def __call__(self):
8        for command in self.commands:
9            command()
Zeilen 4–5

erstellt eine Liste aus den Befehlsargumenten und stellt sicher, dass sie iterierbar ist

Zeile 7–8

erstellt eine lokale Kopie der Befehlsreferenzen in jeder MacroCommand-Instanz. Wenn eine Instanz von MacroCommand aufgerufen wird, wird jeder Befehl in self.commands nacheinander aufgerufen.