Das Befehlsmuster in Java

Das Befehlsmuster in Java

1. Überblick

Das Befehlsmuster ist ein Verhaltensentwurfsmuster und Teil der formalen Liste der Entwurfsmuster vonGoF. Einfach ausgedrückt, das Muster beabsichtigt, encapsulate in an object all the data required for performing a given action (command), anzugeben, einschließlich der aufzurufenden Methode, der Argumente der Methode und des Objekts, zu dem die Methode gehört.

Mit diesem Modell können wirdecouple objects that produce the commands from their consumers angeben. Aus diesem Grund wird das Muster allgemein als Erzeuger-Verbraucher-Muster bezeichnet.

In diesem Lernprogramm erfahren Sie, wie Sie das Befehlsmuster in Java mithilfe objektorientierter und objektfunktionaler Ansätze implementieren. In welchen Anwendungsfällen kann dies hilfreich sein.

2. Objektorientierte Implementierung

In einer klassischen Implementierung erfordert das Befehlsmusterimplementing four components: the Command, the Receiver, the Invoker, and the Client.

Um zu verstehen, wie das Muster funktioniert und welche Rolle jede Komponente spielt, erstellen wir ein grundlegendes Beispiel.

Nehmen wir an, wir möchten eine Textdateianwendung entwickeln. In einem solchen Fall sollten wir alle Funktionen implementieren, die zum Ausführen einiger textdateibezogener Vorgänge erforderlich sind, z. B. Öffnen, Schreiben, Speichern einer Textdatei usw.

Wir sollten die Anwendung also in die vier oben genannten Komponenten aufteilen.

2.1. Befehlsklassen

Ein Befehl ist ein Objekt, dessen Rollestore all the information required for executing an action ist, einschließlich der aufzurufenden Methode, der Methodenargumente und des Objekts (als Empfänger bezeichnet), das die Methode implementiert.

Um eine genauere Vorstellung davon zu erhalten, wie Befehlsobjekte funktionieren, entwickeln wir zunächst eine einfache Befehlsebene, die nur eine einzige Schnittstelle und zwei Implementierungen enthält:

@FunctionalInterface
public interface TextFileOperation {
    String execute();
}
public class OpenTextFileOperation implements TextFileOperation {

    private TextFile textFile;

    // constructors

    @Override
    public String execute() {
        return textFile.open();
    }
}
public class SaveTextFileOperation implements TextFileOperation {

    // same field and constructor as above

    @Override
    public String execute() {
        return textFile.save();
    }
}

In diesem Fall definiert dieTextFileOperation-Schnittstelle die API der Befehlsobjekte, und die beiden ImplementierungenOpenTextFileOperation undSaveTextFileOperation, führen die konkreten Aktionen aus. Ersteres öffnet eine Textdatei, während Letzteres eine Textdatei speichert.

Die Funktionalität eines Befehlsobjekts ist klar erkennbar: dieTextFileOperation-Befehleencapsulate all the information required zum Öffnen und Speichern einer Textdatei, einschließlich des Empfängerobjekts, der aufzurufenden Methoden und der Argumente (in diesem Fall Nr Argumente sind erforderlich, könnten es aber sein).

Es ist erwähnenswert, dassthe component that performs the file operations is the receiver (the TextFile instance).

2.2. Die Empfängerklasse

Ein Empfänger ist ein Objekt, dasperforms a set of cohesive actions ist. Es ist die Komponente, die die eigentliche Aktion ausführt, wenn dieexecute()-Methode des Befehls aufgerufen wird.

In diesem Fall müssen wir eine Empfängerklasse definieren, deren Rolle darin besteht,TextFile Objekte zu modellieren:

public class TextFile {

    private String name;

    // constructor

    public String open() {
        return "Opening file " + name;
    }

    public String save() {
        return "Saving file " + name;
    }

    // additional text file methods (editing, writing, copying, pasting)
}

2.3. Die Invoker-Klasse

Ein Aufrufer ist ein Objekt, dasknows how to execute a given command but doesn’t know how the command has been implemented. kennt. Er kennt nur die Schnittstelle des Befehls.

In einigen Fällen speichert der Aufrufer neben der Ausführung auch Befehle und stellt sie in die Warteschlange. Dies ist nützlich, um einige zusätzliche Funktionen zu implementieren, z. B. die Makroaufzeichnung oder die Funktionen zum Rückgängigmachen und Wiederherstellen.

In unserem Beispiel wird deutlich, dass es eine zusätzliche Komponente geben muss, die dafür verantwortlich ist, die Befehlsobjekte aufzurufen und über dieexecute()-Methode der Befehle auszuführen. This is exactly where the invoker class comes into play.

Schauen wir uns eine grundlegende Implementierung unseres Aufrufers an:

public class TextFileOperationExecutor {

    private final List textFileOperations
     = new ArrayList<>();

    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

Die KlasseTextFileOperationExecutor ist nurthin layer of abstraction that decouples the command objects from their consumers und ruft die Methode auf, die in den Befehlsobjekten vonTextFileOperationgekapselt ist.

In diesem Fall speichert die Klasse die Befehlsobjekte auch inList. Dies ist in der Musterimplementierung natürlich nicht obligatorisch, es sei denn, wir müssen den Ausführungsprozess der Operationen weiter steuern.

2.4. Die Client-Klasse

Ein Client ist ein Objekt, dascontrols the command execution processangibt, welche Befehle ausgeführt werden sollen und in welchen Phasen des Prozesses sie ausgeführt werden sollen.

Wenn wir also mit der formalen Definition des Musters orthodox sein möchten, müssen wir eine Client-Klasse mithilfe der typischenmain-Methode erstellen:

public static void main(String[] args) {
    TextFileOperationExecutor textFileOperationExecutor
      = new TextFileOperationExecutor();
    textFileOperationExecutor.executeOperation(
      new OpenTextFileOperation(new TextFile("file1.txt"))));
    textFileOperationExecutor.executeOperation(
      new SaveTextFileOperation(new TextFile("file2.txt"))));
}

3. Objektfunktionale Implementierung

Bisher haben wir einen objektorientierten Ansatz verwendet, um das Befehlsmuster zu implementieren, was alles gut und schön ist.

Ab Java 8 können wir einen objektfunktionalen Ansatz verwenden, der auf Lambda-Ausdrücken und Methodenreferenzen fürmake the code a little bit more compact and less verbose basiert.

3.1. Verwenden von Lambda-Ausdrücken

Da dieTextFileOperation-Schnittstelle einfunctional interface ist, können wirpass command objects in the form of lambda expressions to the invoker, ohne dieTextFileOperation-Instanzen explizit erstellen zu müssen:

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");

Die Implementierung sieht jetzt viel rationaler und prägnanter aus, da wirreduced the amount of boilerplate code haben.

Trotzdem bleibt die Frage offen: Ist dieser Ansatz besser als der objektorientierte?

Das ist schwierig. Wenn wir davon ausgehen, dass kompakterer Code in den meisten Fällen besseren Code bedeutet, dann ist dies tatsächlich der Fall.

As a rule of thumb, we should evaluate on a per-use-case basis when to resort to lambda expressions.

3.2. Verwenden von Methodenreferenzen

In ähnlicher Weise können wir Methodenreferenzen fürpassing command objects to the invoker: verwenden

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);

In diesem Fall ist die Implementierunga little bit more verbose than the one that uses lambdas, da wir noch dieTextFile-Instanzen erstellen mussten.

4. Fazit

In diesem Artikel haben wir die Schlüsselkonzepte des Befehlsmusters und die Implementierung des Musters in Java mithilfe eines objektorientierten Ansatzes und einer Kombination aus Lambda-Ausdrücken und Methodenreferenzen kennengelernt.

Wie üblich sind alle in diesem Lernprogramm gezeigten Codebeispieleover on GitHub verfügbar.