Command Design Pattern

Der Command-Pattern ist eines der Verhaltensmuster. Das Command-Designmuster wird verwendet, um eine lose Kopplung in einem Anforderungs-Antwort-Modell zu implementieren.

Command Pattern

Beim Command-Muster wird die Anfrage an den „Invoker“ gesendet und dieser leitet sie an das verkapselte „Command“-Objekt weiter. Das Command-Objekt leitet die Anfrage an die entsprechende Methode des „Empfängers“ weiter, um die spezifische Aktion auszuführen. Das Client-Programm erstellt das Empfänger-Objekt und fügt es dann dem Command hinzu. Anschließend erstellt es das Invoker-Objekt und fügt das Command-Objekt hinzu, um eine Aktion auszuführen. Wenn das Client-Programm die Aktion ausführt, wird sie basierend auf dem Command- und Empfänger-Objekt verarbeitet.

Beispiel für das Command Designmuster

Wir werden uns ein reales Szenario ansehen, in dem wir das Befehlsmuster implementieren können. Nehmen wir an, wir möchten ein Dateisystem-Dienstprogramm bereitstellen, das Methoden zum Öffnen, Schreiben und Schließen von Dateien bietet. Dieses Dateisystem-Dienstprogramm sollte mehrere Betriebssysteme wie Windows und Unix unterstützen. Um unser Dateisystem-Dienstprogramm zu implementieren, müssen wir zunächst die Empfängerklassen erstellen, die tatsächlich die gesamte Arbeit erledigen. Da wir in Java in Bezug auf Interface programmieren, können wir ein FileSystemReceiver-Interface und seine Implementierungsklassen für verschiedene Betriebssystemvarianten wie Windows, Unix, Solaris usw. haben.

Empfängerklassen des Befehlsmusters

package com.journaldev.design.command;

public interface FileSystemReceiver {

	void openFile();
	void writeFile();
	void closeFile();
}

Das FileSystemReceiver-Interface definiert den Vertrag für die Implementierungsklassen. Zur Vereinfachung erstelle ich zwei Varianten von Empfängerklassen, die mit Unix- und Windows-Systemen arbeiten sollen.

package com.journaldev.design.command;

public class UnixFileSystemReceiver implements FileSystemReceiver {

	@Override
	public void openFile() {
		System.out.println("Opening file in unix OS");
	}

	@Override
	public void writeFile() {
		System.out.println("Writing file in unix OS");
	}

	@Override
	public void closeFile() {
		System.out.println("Closing file in unix OS");
	}

}
package com.journaldev.design.command;

public class WindowsFileSystemReceiver implements FileSystemReceiver {

	@Override
	public void openFile() {
		System.out.println("Opening file in Windows OS");
		
	}

	@Override
	public void writeFile() {
		System.out.println("Writing file in Windows OS");
	}

	@Override
	public void closeFile() {
		System.out.println("Closing file in Windows OS");
	}

}

Haben Sie das Override-Annotation bemerkt und wenn Sie sich fragen, warum es verwendet wird, lesen Sie bitte Java-Annotationen und Vorteile der Override-Annotation. Jetzt, da unsere Empfängerklassen bereit sind, können wir zur Implementierung unserer Befehlsklassen übergehen.

Befehlsmuster Schnittstelle und Implementierungen

Wir können eine Schnittstelle oder abstrakte Klasse verwenden, um unseren Basisbefehl zu erstellen. Es ist eine Designentscheidung und hängt von Ihren Anforderungen ab. Wir entscheiden uns für eine Schnittstelle, weil wir keine Standardimplementierungen haben.

package com.journaldev.design.command;

public interface Command {

	void execute();
}

Jetzt müssen wir Implementierungen für alle verschiedenen Arten von Aktionen erstellen, die vom Empfänger ausgeführt werden. Da wir drei Aktionen haben, werden wir drei Befehlsimplementierungen erstellen. Jede Befehlsimplementierung leitet die Anfrage an die entsprechende Methode des Empfängers weiter.

package com.journaldev.design.command;

public class OpenFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public OpenFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		// Der Öffnungsbefehl leitet die Anfrage an die Methode openFile weiter
		this.fileSystem.openFile();
	}

}
package com.journaldev.design.command;

public class CloseFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public CloseFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		this.fileSystem.closeFile();
	}

}
package com.journaldev.design.command;

public class WriteFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public WriteFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		this.fileSystem.writeFile();
	}

}

Jetzt haben wir Empfänger- und Befehlsimplementierungen bereit, also können wir zur Implementierung der Aufruferklasse übergehen.

Befehlsmuster-Aufruferklasse

Der Aufrufer ist eine einfache Klasse, die den Befehl kapselt und die Anfrage an das Befehlsobjekt weitergibt, um sie zu verarbeiten.

package com.journaldev.design.command;

public class FileInvoker {

	public Command command;
	
	public FileInvoker(Command c){
		this.command=c;
	}
	
	public void execute(){
		this.command.execute();
	}
}

Unsere Implementierung des Dateisystemdienstprogramms ist bereit, und wir können damit beginnen, ein einfaches Client-Programm für das Befehlsmuster zu schreiben. Bevor wir das tun, werde ich jedoch eine Hilfsmethode bereitstellen, um das entsprechende FileSystemReceiver-Objekt zu erstellen. Da wir die Systemklasse verwenden können, um Informationen zum Betriebssystem zu erhalten, werden wir dies tun, ansonsten können wir das Factory-Muster verwenden, um den geeigneten Typ basierend auf der Eingabe zurückzugeben.

package com.journaldev.design.command;

public class FileSystemReceiverUtil {
	
	public static FileSystemReceiver getUnderlyingFileSystem(){
		 String osName = System.getProperty("os.name");
		 System.out.println("Underlying OS is:"+osName);
		 if(osName.contains("Windows")){
			 return new WindowsFileSystemReceiver();
		 }else{
			 return new UnixFileSystemReceiver();
		 }
	}
	
}

Lassen Sie uns nun dazu übergehen, unser Beispielprogramm für das Befehlsmuster zu erstellen, das unser Dateisystemdienstprogramm verwendet.

package com.journaldev.design.command;

public class FileSystemClient {

	public static void main(String[] args) {
		// Erstellen des Empfängerobjekts
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		// Befehl erstellen und mit dem Empfänger verknüpfen
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		// Erstellen des Aufrufers und Verknüpfen mit dem Befehl
		FileInvoker file = new FileInvoker(openFileCommand);
		
		// Aktion auf Aufruferobjekt ausführen
		file.execute();
		
		WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
		file = new FileInvoker(writeFileCommand);
		file.execute();
		
		CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
		file = new FileInvoker(closeFileCommand);
		file.execute();
	}

}

Beachten Sie, dass der Client dafür verantwortlich ist, den geeigneten Typ des Befehls zu erstellen. Wenn Sie beispielsweise eine Datei schreiben möchten, sollten Sie kein CloseFileCommand-Objekt erstellen. Das Client-Programm ist auch dafür verantwortlich, den Empfänger dem Befehl und dann den Befehl der Aufruferklasse anzufügen. Die Ausgabe des obigen Beispielprogramms für das Befehlsmuster lautet:

Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS

Klassen Diagramm für das Befehlsmuster

Hier ist das Klassendiagramm für unsere Implementierung des Dateisystemdienstprogramms.

Wichtige Punkte des Befehlsmusters

  • Befehl ist der Kern des Befehls-Designmusters, das den Vertrag für die Implementierung definiert.
  • Die Implementierung des Empfängers ist von der Befehlsimplementierung getrennt.
  • Befehlsimplementierungsklassen wählen die Methode aus, die auf dem Empfängerobjekt aufgerufen werden soll. Für jede Methode im Empfänger gibt es eine Befehlsimplementierung. Es fungiert als Brücke zwischen Empfänger und Aktionsmethoden.
  • Der Invoker-Klasse leitet einfach die Anfrage des Clients an das Befehlsobjekt weiter.
  • Der Client ist dafür verantwortlich, den geeigneten Befehl und die Empfängerimplementierung zu instanziieren und sie dann miteinander zu verknüpfen.
  • Der Client ist auch dafür verantwortlich, das Invoker-Objekt zu instanziieren und das Befehlsobjekt damit zu verknüpfen und die Aktionsmethode auszuführen.
  • Das Befehls-Designmuster ist leicht erweiterbar. Wir können neue Aktionsmethoden in Empfängern hinzufügen und neue Befehlsimplementierungen erstellen, ohne den Client-Code zu ändern.
  • Der Nachteil des Befehls-Designmusters besteht darin, dass der Code bei einer großen Anzahl von Aktionsmethoden und aufgrund vieler Verknüpfungen umfangreich und verwirrend wird.

Befehlsentwurfsmuster JDK Beispiel

Runnable-Schnittstelle (java.lang.Runnable) und Swing Action (javax.swing.Action) verwenden das Befehlsentwurfsmuster.

Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern