Back to: Java Design Patterns
Command Design Pattern in Java
In this article, I am going to discuss the Command Design Pattern in Java with Examples. Please read our previous article where we discussed the Chain of Responsibility Design Pattern in Java with Examples. The Command Design Pattern falls under the category of Behavioral Design Pattern. In this article, we will explore the fundamental principles and benefits of the Command design pattern, highlighting its significance in various software development scenarios.
What is Command Design Pattern?
According to the Gang of Four definitions, Command Design Pattern is used to encapsulate a request as an object (i.e. a command) and pass it to an invoker, wherein the invoker does not know how to serve the request but uses the encapsulated command to perform an action.
In software development, managing and encapsulating actions or operations is a common requirement. The Command design pattern provides an elegant solution to this challenge by encapsulating requests as objects, enabling decoupling between senders and receivers.
This pattern allows for flexibility in command execution, undo/redo functionality, and extensibility through the dynamic addition of new commands.
It promotes loose coupling and enables the parameterization of clients with different requests, allowing them to operate on objects without knowing the specific operations being performed. The pattern consists of four primary components: the command interface, concrete command classes, the invoker, and the receiver. The command interface defines the common operations for executing and undoing a command, while concrete command classes encapsulate specific actions and maintain references to the receiver. The invoker triggers the command execution, and the receiver performs the requested action.
Example to Understand Command Design Pattern
Please have a look at the following image. As you can see in the below image, the client will create the command object. The command object involves three things. First, the Command Object has the Request (i.e. what to do?). Second, it also has the Receiver Object Reference. The Receiver Object is nothing but the object which will handle the request. Third, the command object also has the Execute method. The Execute method will call the receiver object method and the receiver object method will handle the request.
As per the Command Design Pattern, the Command Object will be passed to the Invoker Object. The Invoker Object does not know how to handle the request. What the Invoker will do is, it will call the Execute method of the Command Object. The Execute method of the command object will call the Receiver Object Method. The Receiver Object Method will perform the necessary action to handle the request. For a better understanding please have a look at the following diagram.
This is how the command design pattern works. As per the Command Design Pattern, the command object has three things. The first one is the request i.e. the command. The second one is the Receiver object reference and the third one is the Execute method which will call the receiver object method to handle the request.
Implementing Command Design Pattern in Java
The Command pattern can be used to implement a file-downloading system. The actions can be represented as commands, where each command encapsulates the necessary information for performing an action on a file. The UML Diagram of this example is given below using Command Design Pattern.
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called command.
Step 3: In the project, create a new file called Action.java. Add the following code to the file:
public interface Action { public void execute(); }
This is the interface from which other concrete classes will extend.
Step 4: In the project, create a new file called File.java. Add the following code to the file:
public class File { public void download() {System.out.println("File downloaded successfully.");} public void open() {System.out.println("File opened successfully.");} }
Step 5: In the project, create a new file called Download.java. Add the following code to the file:
public class Download implements Action { private File f; public Download(File f) { this.f = f; } @Override public void execute () { f.download(); } }
Step 6: In the project, create a new file called Open.java. Add the following code to the file:
public class Open implements Action { private File f; public Open(File f) { this.f = f; } @Override public void execute () { f.open(); } }
Step 7: In the project, create a new file called Options.java. Add the following code to the file:
public class Options { private Action download, open; public Options(Action download, Action open) { this.download = download; this.open = open; } public void clickDownload() {download.execute();} public void clickOpen() {open.execute();} }
Step 8: In the project, create a new file called CommandPatternDemo.java. This class will contain the main() function. Add the following code to CommandPatternDemo.java:
public class CommandPatternDemo { public static void main(String[] args) { File f = new File(); Options o = new Options(new Download(f), new Open(f)); o.clickDownload(); o.clickOpen(); } }
The main() function creates an object of type Options. This object is given objects of type Download and Open. These objects can then be used to perform open and download operations on the File.
Step 9: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.
Congratulations! You now know how to implement Command Design Pattern in Java.
UML Diagram of Command Design Pattern:
Now, let us see the Command Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.
The classes can be described as follows:
- Object: This class contains the definitions for the functions. These functions will later be implemented in the concrete classes.
- ConcreteObject: This class implements the functions defined in aforementioned interface.
- DriverClass: This class contains the main() function and is responsible for handling the simulation of the program.
- Handler: This class handles the objects and acts as a “middleman” between the DriverClass and the objects.
- AnotherObject: This object is performing actions on the ConcreteObject class.
Advantages of Command Design Pattern in Java
The advantages of using the Command Design Pattern in Java are as follows:
- Decoupling of Sender and Receiver: The Command pattern decouples the sender of a request from its receiver. Senders interact only with the command objects, without having knowledge of the receivers or the specific operations being performed. This loose coupling promotes flexibility, as it allows for dynamic changes in the command implementation or receiver without affecting the sender or other parts of the system.
- Flexibility in Command Execution: The Command pattern provides flexibility in executing commands. Command objects can be stored, queued, or serialized, allowing for delayed execution, scheduling, or remote invocation. This flexibility enables the implementation of advanced features such as command logging, undo/redo functionality, or transactional behavior.
- Undo/Redo Functionality: By encapsulating actions as command objects, the Command pattern inherently supports undo/redo functionality. Commands can store the necessary state to reverse their effects, enabling the system to roll back or replay operations. This capability is particularly useful in applications where users need to backtrack or repeat actions, such as text editors, graphic design tools, or transactional systems.
- Extensibility through Dynamic Addition of Commands: The Command pattern facilitates the dynamic addition of new commands without modifying existing code. New command classes can be introduced, implementing the command interface, and seamlessly integrated into the system. This extensibility allows for easy integration of new features or functionalities, promoting the Open-Closed Principle and avoiding the need to modify existing code.
- Encapsulation and Separation of Concerns: The Command pattern encapsulates operations or actions as separate objects, promoting the separation of concerns. Each command class focuses on a specific action or operation, keeping the codebase modular and maintainable. This separation enhances code readability, testability, and reusability, as each command object encapsulates a single responsibility.
Disadvantages of Command Design Pattern in Java
The disadvantages of using the Command Design Pattern in Java are as follows:
- Increased Complexity: Implementing the Command pattern can introduce additional complexity to the system. The pattern requires the creation of command classes for each individual operation, which may lead to a proliferation of classes. Managing a large number of command classes can become challenging, especially when dealing with complex systems with numerous commands and variations.
- Overhead of Command Objects: The Command pattern introduces the overhead of creating and maintaining command objects. Each command object encapsulates an action and potentially stores additional state information. This additional object overhead can impact memory usage, especially in scenarios where a large number of commands need to be created and managed.
- Potential Performance Impact: The use of command objects can introduce performance overhead. Invoking commands requires passing through multiple layers of abstraction, including the invoker, command objects, and the receiver. This additional indirection can impact the overall execution time, especially in performance-critical systems. Careful consideration should be given to the performance implications of using the Command pattern and potential optimizations may be required.
- Difficulty in Handling Complex Undo/Redo Scenarios: While the Command pattern provides a natural way to implement undo/redo functionality, managing complex undo/redo scenarios can be challenging. Handling dependencies between commands, managing the order of execution, and maintaining the necessary state information for undoing and redoing operations can become complex. As the complexity of undo/redo requirements grows, the implementation of the Command pattern may become more intricate and harder to maintain.
- Lack of Visibility and Traceability: The Command pattern can introduce difficulties in tracking and understanding the flow of commands through the system. The direct link between the sender and the receiver is lost, as commands act as intermediaries. This loss of visibility can make it harder to trace the execution of commands, debug issues, and understand the overall system behavior, especially in complex systems with multiple levels of indirection.
- Increased Code Size and Maintenance: The Command pattern can lead to an increase in code size and maintenance efforts. Each command requires a separate class, leading to a larger codebase. This increase in code size can make the system more complex to understand, navigate, and maintain. Additionally, modifications or updates to commands may require changes in multiple places, leading to additional maintenance efforts.
In the next article, I am going to discuss Interpreter Design Pattern in Java with Examples. Here, in this article, I try to explain Command Design Pattern in Java with Examples. I hope you understood the need for and use of the Command Design Pattern in Java.