Command Pattern
The Command pattern is a behavioral design pattern that turns a request into a stand-alone object. This object contains all information about the request and allows you to execute different commands/requests dynamically, queue or log requests, and support undoable operations.
It works by defining a common interface (Command
) with an execute()
method, which is implemented by concrete command classes. These concrete commands contain the specifics of the operation and often hold a reference to a receiver object that performs the work. An invoker object holds and triggers these commands without knowing their specific implementation. When the invoker calls the execute()
method on a command, the command in turn calls the appropriate method on its receiver. This structure allows for easy addition of new commands without modifying existing code, supports operations like undo/redo, enables queueing of requests, and provides a way to execute requests dynamically.
Structure
The Command pattern consists of the following key components:
- Command: an interface or an abstract class that defines methods to execute operations (execute and undo)
- Concrete Command: implements the Command interface, and the execute() method invokes specific actions on the corresponding Receiver
- Invoker: is responsible for initiating requests. It holds the Command objects and invokes the execute() method
- Receiver: contains the business logic on how to process a request received from the Invoker’s execute() method
- Client: creates Command objects with the required data, and sets the receiver for the Command
When to use the Command Pattern?
- When you want to separate the invoker and the handler/receiver for an operation/command (
Single Responsibility Principle
) - When you want to introduce new operations/commands in the future without breaking the existing code (
Open/Closed Principle
)
Implementation Example
The code below demonstrates a simple implementation of the Command design pattern to manage a job queue for email and print tasks in Java. The Solution
class acts as the client, creating a JobQueue
(invoker) and populating it with EmailJob
and PrintJob
objects (concrete commands). These jobs are associated with EmailService
and PrintService
objects (receivers) respectively. The Job
interface defines the command contract with an execute()
method. Concrete command classes (EmailJob
and PrintJob
) encapsulate the request details and delegate the actual work to their respective services. The JobQueue
class manages a queue of jobs and processes them sequentially. This structure allows for easy addition of new job types and decouples the job requester from the job executor, providing flexibility and extensibility in handling various types of tasks.
import java.util.Queue;
import java.util.LinkedList;
// Client
public class Solution {
public static void main(String[] args) {
JobQueue jobQueue = new JobQueue();
EmailService emailService = new EmailService();
PrintService printService = new PrintService();
jobQueue.addJob(new EmailJob("hello@abcd.com", emailService));
jobQueue.addJob(new PrintJob("Commands.pdf", printService));
jobQueue.addJob(new EmailJob("world@abcd.com", emailService));
jobQueue.addJob(new PrintJob("Report.docx", printService));
jobQueue.processJobs();
}
}
// Command interface
interface Job {
void execute();
}
// Concrete Commands
class EmailJob implements Job {
private String to;
private EmailService emailService;
public EmailJob(String to, EmailService emailService) {
this.to = to;
this.emailService = emailService;
}
public void execute() {
emailService.sendEmail(to);
}
}
class PrintJob implements Job {
private String document;
private PrintService printService;
public PrintJob(String document, PrintService printService) {
this.document = document;
this.printService = printService;
}
public void execute() {
printService.printDocument(document);
}
}
// Receivers
class EmailService {
public void sendEmail(String to) {
System.out.println("----------EmailService----------");
System.out.println("Sending email to: " + to);
System.out.println();
}
}
class PrintService {
public void printDocument(String document) {
System.out.println("----------PrintService----------");
System.out.println("Printing document: " + document);
System.out.println();
}
}
// Invoker
class JobQueue {
private Queue<Job> jobs = new LinkedList<>();
public void addJob(Job job) {
jobs.offer(job);
}
public void processJobs() {
while (!jobs.isEmpty()) {
Job job = jobs.poll();
job.execute();
}
}
}
The output of the following program will be:
----------EmailService----------
Sending email to: hello@abcd.com
----------PrintService----------
Printing document: Commands.pdf
----------EmailService----------
Sending email to: world@abcd.com
----------PrintService----------
Printing document: Report.docx
Enjoyed the read? Give it a thumbs up 👍🏻!