The Command design pattern.

The Command Pattern belongs to the Behavioral Design Patterns category. It is utilized to achieve loose coupling in a request-response model.

The Command Pattern is a design pattern.

In the command pattern, the invoker receives the request and passes it to the encapsulated command object. The command object then forwards the request to the receiver, which performs the specific action. The client program first creates the receiver object and associates it with the command. Then it creates the invoker object and associates it with the command to carry out an action. When the client program executes the action, it is processed according to the command and receiver objects.

One example of the Command design pattern.

We will examine an actual situation where we can utilize the Command pattern. Let’s assume that we aim to develop a File System utility that includes functions to open, write, and close files. This utility should be compatible with various operating systems such as Windows and Unix. In order to create our File System utility, the initial step is to establish the receiver classes responsible for executing the required tasks. As we follow the java programming language’s practice of coding based on interfaces, we can define a FileSystemReceiver interface along with its implementation classes for different operating systems like Windows, Unix, Solaris, etc.

Receiver classes in the Command Pattern.

package com.scdev.design.command;

public interface FileSystemReceiver {

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

The File System Receiver interface lays out the criteria for the implementation classes. To simplify matters, I am developing two variations of receiver classes that will function with Unix and Windows operating systems.

package com.scdev.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.scdev.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");
	}

}

If you have noticed the Override annotation and are curious about its purpose, please refer to the benefits of java annotations and override annotation. With our receiver classes prepared, we can now proceed to implementing our Command classes.

Interface and Implementations for the Command Pattern.

Whether we choose to utilize an interface or an abstract class to establish our foundational Command is a design choice that solely relies on the specific needs of our project. In this scenario, we have decided to employ an interface as there are no pre-existing implementations in place.

package com.scdev.design.command;

public interface Command {

	void execute();
}

We now have to generate implementations for each type of action carried out by the receiver. As we have three actions, we will develop three Command implementations. Each Command implementation will direct the request to the respective method of the receiver.

package com.scdev.design.command;

public class OpenFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public OpenFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		//open command is forwarding request to openFile method
		this.fileSystem.openFile();
	}

}
package com.scdev.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.scdev.design.command;

public class WriteFileCommand implements Command {

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

}

We can proceed to implementing the invoker class now that we have already prepared the receiver and command implementations.

Invoker class for the Command Pattern

The Invoker class acts as a straightforward wrapper for the Command, forwarding the request to the command object for execution.

package com.scdev.design.command;

public class FileInvoker {

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

We have finished implementing our file system utility and can now proceed to create a basic command pattern client program. However, beforehand, I will create a utility method to generate the necessary FileSystemReceiver object. We can utilize the System class to gather operating system information, or alternatively, we can employ the Factory pattern to determine the appropriate type based on the input.

package com.scdev.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();
		 }
	}
	
}

Now, we’ll proceed to develop our client program for the command pattern example, which will make use of our file system utility.

package com.scdev.design.command;

public class FileSystemClient {

	public static void main(String[] args) {
		//Creating the receiver object
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		//creating command and associating with receiver
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		//Creating invoker and associating with Command
		FileInvoker file = new FileInvoker(openFileCommand);
		
		//perform action on invoker object
		file.execute();
		
		WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
		file = new FileInvoker(writeFileCommand);
		file.execute();
		
		CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
		file = new FileInvoker(closeFileCommand);
		file.execute();
	}

}

Please be aware that it is the client’s responsibility to generate the command object of the appropriate type. To illustrate, if you intend to write a file, you should not create a CloseFileCommand object. The client program is also accountable for connecting the receiver to the command and then the command to the invoker class. The result of the aforementioned program that demonstrates the command pattern is:

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

The diagram representing the Command Pattern.

This is the class diagram representing our implementation of the file system utility.

Key points of the Command Pattern include its significance and essential aspects.

  • Command is the core of command design pattern that defines the contract for implementation.
  • Receiver implementation is separate from command implementation.
  • Command implementation classes chose the method to invoke on receiver object, for every method in receiver there will be a command implementation. It works as a bridge between receiver and action methods.
  • Invoker class just forward the request from client to the command object.
  • Client is responsible to instantiate appropriate command and receiver implementation and then associate them together.
  • Client is also responsible for instantiating invoker object and associating command object with it and execute the action method.
  • Command design pattern is easily extendible, we can add new action methods in receivers and create new Command implementations without changing the client code.
  • The drawback with Command design pattern is that the code gets huge and confusing with high number of action methods and because of so many associations.

An example of the Command Design Pattern in JDK.

The command pattern is implemented by the Runnable interface (java.lang.Runnable) and the Swing Action (javax.swing.Action).

 

more tutorials

Strategy Design Pattern in Java tutorial(Opens in a new browser tab)

BroadcastReceiver Example Tutorial on Android(Opens in a new browser tab)

Python Compiler Error during package installation?(Opens in a new browser tab)

Ensuring thread safety in Java Singleton Classes(Opens in a new browser tab)

Spring Component annotation(Opens in a new browser tab)

Leave a Reply 0

Your email address will not be published. Required fields are marked *


广告
Closing in 10 seconds
bannerAds