We want to represent a request or a method call as an object. Information about parameters passed and the actual operation is encapsulated in an object called command.
The advantage of a command pattern is that what would have been a method call is now an object which can be stored for later execution or sent to other parts of code.
We can now even queue our command objects and execute them later.
2. Description
2.1 Implementation
Command interface must define a method which executes the command
Implement this interface in class for each request or operation type we want to implement. Command should also allow for undo operation if your system needs it.
Each concrete command knows exactly which operation it needs. All it needs is parameters for the operation if required and the receiver instance on which operation is invoked.
Client creates the command instance and sets up the receiver and all required parameters.
The command instance is then ready to be sent to other parts of the code. Invoker is the code that actually uses command instance and invokes the execution on the command.
2.2 Considerations
2.2.1 Implementation
You can support undo and redo in your commands. This makes them really useful for systems with complex user interactions like workflow designer.
If your command is simple i.e. if it doesn't have to undo feature, doesn't have any state and simply hides a particular function And its arguments then you can reuse same command object for the same type of request
For commands that are going to be queued for long durations, pay attention to size of state maintained by them
2.2.2 Design
Commands can be inherited from other commands to reuse portions of code and build the base
You can also compose commands with other commands as well. These macro commands will have one or more sub-commands executed in sequence to complete a request
For implementing undo feature in your command you can make use of memento design pattern, which allows commands to store the state information of receiver without knowing about internal objects used by the receiver
2.3 Pitfalls
Things get a bit controversial when it comes to returning values and error handling with command
Error handling is difficult to implement without coupling the command with the client. In cases where a client needs to know a return value of execution, it's the same situation.
In code where invoker is running in a different thread, which is very common in situations where command pattern is used, error handling and returns values get lot more complicated to handle
3. Usage
3.1 java.lang.Runnable
3.2 The Action class in struts framework
4. Comparison with Strategy
Command
Strategy
Command contains which operation is being executed by the receiver
Strategy actually contains how the operation is to be carried out
Command encapsulates an actions
Strategy encapsulates a particular algorithm
5. Example
//Interface implemented by all concrete
//command classes
interface Command {
void execute();
}
//A Concrete implementation of Command.
class AddMemberCommand implements Command {
private String emailAddress;
private String listName;
private EWSService receiver;
public AddMemberCommand(String email, String listName, EWSService service) {
this.emailAddress = email;
this.listName = listName;
this.receiver = service;
}
@Override
public void execute() {
receiver.addMember(emailAddress, listName);
}
}
//This class is the receiver.
class EWSService {
//Add a new member to mailing list
public void addMember(String contact, String contactGroup) {
//contact exchange
System.out.println("Added "+contact +" to "+contactGroup);
}
//Remove member from mailing list
public void removeMember(String contact, String contactGroup) {
//contact exchange
System.out.println("Removed "+contact +" from "+contactGroup);
}
}
//Throw Away POC code DON'T USE in PROD
//This is invoker actually executing commands.
//starts a worker thread in charge of executing commands
// MailTasksRunner itself is singleton
class MailTasksRunner implements Runnable {
private Thread runner;
private List<Command> pendingCommands;
private volatile boolean stop;
private static final MailTasksRunner RUNNER = new MailTasksRunner();
public static final MailTasksRunner getInstance() {
return RUNNER;
}
private MailTasksRunner() {
pendingCommands = new LinkedList<>();
runner = new Thread(this);
runner.start();
}
//Run method takes pending commands and executes them.
@Override
public void run() {
while (true) {
Command cmd = null;
synchronized (pendingCommands) {
if (pendingCommands.isEmpty()) {
try {
pendingCommands.wait();
} catch (InterruptedException e) {
System.out.println("Runner interrupted");
if (stop) {
System.out.println("Runner stopping");
return;
}
}
} else {
cmd = pendingCommands.remove(0);
}
}
if (cmd == null)
return;
cmd.execute();
}
}
//Giving it a command will schedule it for later execution
public void addCommand(Command cmd) {
synchronized (pendingCommands) {
pendingCommands.add(cmd);
pendingCommands.notifyAll();
}
}
public void shutdown() {
this.stop = true;
this.runner.interrupt();
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
EWSService service = new EWSService();
Command c1 = new AddMemberCommand("a@a.com", "spam", service);
MailTasksRunner.getInstance().addCommand(c1);
Command c2 = new AddMemberCommand("b@b.com", "spam", service);
MailTasksRunner.getInstance().addCommand(c2);
Thread.sleep(3000);
MailTasksRunner.getInstance().shutdown();
}
}