Command Pattern is een van de Gedragsontwerppatronen. Het commando-ontwerppatroon wordt gebruikt om losse koppeling te implementeren in een request-response model.
Commando patroon
In het commando-ontwerppatroon wordt het verzoek naar de
invoker
gestuurd en de invoker geeft het door aan het ingekapselde commando
object. Het commando-object geeft het verzoek door aan de juiste methode van de Ontvanger
om de specifieke actie uit te voeren. Het clientprogramma maakt het ontvangerobject aan en koppelt het vervolgens aan het commando. Vervolgens maakt het het invoker-object aan en koppelt het commando-object om een actie uit te voeren. Wanneer het clientprogramma nu de actie uitvoert, wordt deze verwerkt op basis van het commando- en ontvangerobject.
Voorbeeld van het Commando-ontwerppatroon
We zullen een real-life scenario bekijken waarin we het Command-patroon kunnen implementeren. Laten we zeggen dat we een hulpprogramma voor het bestandssysteem willen bieden met methoden om een bestand te openen, te schrijven en te sluiten. Dit bestandssysteemhulpprogramma moet meerdere besturingssystemen ondersteunen, zoals Windows en Unix. Om ons bestandssysteemhulpprogramma te implementeren, moeten we allereerst de ontvangerklassen maken die daadwerkelijk al het werk zullen doen. Aangezien we coderen in termen van de interface in Java, kunnen we een FileSystemReceiver
-interface hebben en implementatieklassen ervan voor verschillende besturingssysteemsmaken zoals Windows, Unix, Solaris, enzovoort.
Command Pattern Receiver Classes
package com.journaldev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
Het FileSystemReceiver
-interface definieert het contract voor de implementatieklassen. Voor eenvoud maak ik twee smaken van ontvangerklassen om samen te werken met Unix- en Windowssystemen.
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");
}
}
Heb je de Override-annotatie opgemerkt, en als je je afvraagt waarom het wordt gebruikt, lees dan java-annotaties en voordelen van de Override-annotatie. Nu onze ontvangerklassen klaar zijn, kunnen we doorgaan met het implementeren van onze Command-klassen.
Command Pattern Interface en Implementaties
We kunnen een interface of abstracte klasse gebruiken om onze basisopdracht te maken, het is een ontwerpbeslissing en hangt af van uw vereiste. We kiezen voor een interface omdat we geen standaardimplementaties hebben.
package com.journaldev.design.command;
public interface Command {
void execute();
}
Nu moeten we implementaties maken voor alle verschillende soorten acties uitgevoerd door de ontvanger. Aangezien we drie acties hebben, zullen we drie Command-implementaties maken. Elke Command-implementatie zal het verzoek doorsturen naar de juiste methode van de ontvanger.
package com.journaldev.design.command;
public class OpenFileCommand implements Command {
private FileSystemReceiver fileSystem;
public OpenFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
// open commando stuurt het verzoek door naar de openFile-methode
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();
}
}
Nu hebben we ontvanger en commando-implementaties klaar, dus we kunnen overgaan tot implementatie van de aanroeperklasse.
Command Pattern Aanroeperklasse
Aanroeper is een eenvoudige klasse die de Commando omvat en het verzoek doorgeeft aan het commando-object om het te verwerken.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
Onze implementatie van het bestandssysteemhulpprogramma is gereed en we kunnen doorgaan met het schrijven van een eenvoudig cliëntprogramma voor het commandopatroon. Maar voordat ik dat doe, zal ik een hulpmethode verstrekken om het juiste FileSystemReceiver
-object te maken. Aangezien we de System-klasse kunnen gebruiken om informatie over het besturingssysteem te verkrijgen, zullen we deze gebruiken, anders kunnen we het Factory-patroon gebruiken om het juiste type terug te geven op basis van de invoer.
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();
}
}
}
Laten we nu doorgaan met het maken van ons cliëntprogramma voor het commandopatroon dat ons bestandssysteemhulpprogramma zal gebruiken.
package com.journaldev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
// Het ontvangende object maken
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
// Commando maken en koppelen aan de ontvanger
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
// Uitvoerder maken en koppelen aan het commando
FileInvoker file = new FileInvoker(openFileCommand);
// Actie uitvoeren op uitvoerderobject
file.execute();
WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
file = new FileInvoker(writeFileCommand);
file.execute();
CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
file = new FileInvoker(closeFileCommand);
file.execute();
}
}
Merk op dat de cliënt verantwoordelijk is voor het maken van het juiste type commando-object. Als je bijvoorbeeld een bestand wilt schrijven, moet je geen CloseFileCommand
-object maken. Het cliëntprogramma is ook verantwoordelijk voor het koppelen van de ontvanger aan het commando en vervolgens het commando aan de uitvoerderklasse. De uitvoer van het bovenstaande voorbeeldprogramma voor het commandopatroon is:
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
Klassen diagram van het commandopatroon
Hier is het klassendiagram voor onze bestandssysteem-hulpprogramma-implementatie.
Belangrijke punten van het Command-patroon
- Command is de kern van het commando-ontwerppatroon dat het contract voor de implementatie definieert.
- De implementatie van de ontvanger is gescheiden van de implementatie van het commando.
- Implementatieklassen van het commando kiezen de methode om aan te roepen op het ontvangerobject, voor elke methode in het ontvangerobject zal er een commando-implementatie zijn. Het werkt als een brug tussen ontvanger en actiemethoden.
- De invoker-klasse stuurt gewoon het verzoek door van de client naar het commando-object.
- De client is verantwoordelijk voor het instantiëren van het juiste commando en de ontvangerimplementatie en vervolgens het aan elkaar koppelen ervan.
- De client is ook verantwoordelijk voor het instantiëren van het invoker-object en het associëren van het commando-object ermee en het uitvoeren van de actiemethode.
- Het commando-ontwerppatroon is gemakkelijk uitbreidbaar, we kunnen nieuwe actiemethoden toevoegen aan ontvangers en nieuwe commando-implementaties maken zonder de clientcode te wijzigen.
- Het nadeel van het commando-ontwerppatroon is dat de code enorm en verwarrend wordt bij een groot aantal actiemethoden en vanwege zoveel associaties.
Command Design Pattern JDK Voorbeeld
Runnable interface (java.lang.Runnable) en Swing Actie (javax.swing.Action) gebruiken het commando patroon.
Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern