Command Pattern in Java


Reading time: 15 minutes

The command pattern is a behavioral-based design pattern that encapsulates a request/action in an object without knowing the internal operations.

The idea is to create an interface of commands as objects that can be called while being able to decouple the object that performs the action and the object that invokes the action. We have explored it in Java.

Problem

Take for instance, the typical programs we wrote when we coded our first programs. Often times we would use a bunch of branch statements to perform such actions.

//Maybe we wouldn't make an electronic door class 
//as one of our first programs but this is an example of what we may write.
ElectronicDoor door = new ElectronicDoor();
switch (door.command) {
case 1: 
    door.open();
    break;
case 2:
    door.close();
    break;
}

The main issue is that other classes that use the Calculator object will need to know the calculator commands which creates tightly coupled code while exposing the implementation of the Caclulator class.

Say we want to change the names of the functions in the calculator class, that means all the classes that used the calculator class will need to change their code too.

Futhermore, what if we wanted to undo actions that we did with our calculator? Or perhaps we wanted to put these action requests into a queue to be performed later? Surely it is possible to do but it would be rather messy to implement and probably more difficult to understand.

Solution

This is where the command pattern comes in handy to help solve these problems that were previously mentioned.

Structure

command_pattern_java
The general command pattern structure is comprised of 5 components. The names may vary depending on the person, but they serve their purpose.

  1. Command Interface, provides an interface that all concrete command classes will use.
  2. Concrete Command classes, calls a specific method in the Receiver class.
  3. Receiver, the class that does all the work behind the scenes where all the inner details are.
  4. Invoker, the class that calls the Command objects's execute function when needed to perform the request.
  5. Client, the client that can parameterize and pick what commands they want to request.

Implementation

The following is a simple and realistic use of the command pattern implemented.

//The receiver class
public class ElectronicDoor {
	private boolean isLocked;
	private boolean isOpened;
	
	public void open() {
		if (!isLocked) {
			isOpened = true;
		}
	}
    
	public void close() {
		isOpened = false;
	}
	
	public void lock() {
		if (!isOpened) {
			isLocked = true;
		}
	}
	
	public void unlock() {
		isLocked = false;
	}
	
	public void showStatus() {
		String lockStatus = isLocked ? "locked" : "unlocked";
		String openStatus = isOpened ?  "open" : "closed";
		
        System.out.println("The door is currently " + openStatus + " and is " + lockStatus);
	}
}

The ElectronicDoor class is considered as the receiver where all the concrete command classes will use the receiver to invoke methods.This class will be encapsulated by the commands invoking the methods from this class.

It's important to note that all the inner details of how it's implemented goes into this class.

//Command interface
public interface Command {
	public void execute();
}

//Concrete Command Class
public class LockDoorCommand implements Command {
	ElectronicDoor door;
	
	public LockDoorCommand(ElectronicDoor door) {
		this.door = door;
	}

	public void execute() {
		door.lock();
	}
}

//Concrete Command Class
public class UnlockDoorCommand implements Command {
	ElectronicDoor door;
	
	public UnlockDoorCommand(ElectronicDoor door) {
		this.door = door;
	}
	
	public void execute() {
		door.unlock();
	}
}

//Concrete Command Class
public class OpenDoorCommand implements Command {
	ElectronicDoor door;
	
	public OpenDoorCommand(ElectronicDoor door) {
		this.door = door;
	}
	
	public void execute() {
		door.open();
	}
}

//Concrete Command Class
public class CloseDoorCommand implements Command {
	ElectronicDoor door;
	
	public CloseDoorCommand(ElectronicDoor door) {
		this.door = door;
	}
	
	public void execute() {
		door.close();
	}
}

//Concrete Command Class
public class ShowDoorStatusCommand implements Command {
	ElectronicDoor door;
	
	public ShowDoorStatusCommand(ElectronicDoor door) {
		this.door = door;
	}
	
	public void execute() {
		door.showStatus();
	}
}

The command pattern uses an interface known as the command. The command interface provides methods which the concrete command classes will implement.

If you need the undo/redo functionality, simply add the methods in the command interface.

The concrete command class will typically call on one of the receivers methods. In this case, the receiver is the ElectronicDoor class.

The concrete command classes are rather simplistic but they provide interfacing with the receiver class. The classes are generally supposed to be simple which makes it very easy to debug due to being simple.

Note: It is very standard to name classes after the design pattern you are using as this helps indicate intention along with improving readability in code and meaningful naming.
ex: LockDoorCommand, OpenDoorCommand, ShowDoorStatusCommand

//The invoker, takes in all the commands and excutes them
public class DoorControlPanel {
	private Command command;
	
	public void setCommand(Command command) {
		this.command = command;
	}
	
	public void pressCommand() {
		command.execute();
	}
}

//The client who uses the invoker
public class Client {
	public static void main(String[] args) {
		ElectronicDoor door = new ElectronicDoor();
		DoorControlPanel controls = new DoorControlPanel();
		
		Command openDoor = new OpenDoorCommand(door);
		Command closeDoor = new CloseDoorCommand(door);
		Command unlockDoor = new UnlockDoorCommand(door);
		Command lockDoor = new LockDoorCommand(door);
		Command showStatus = new ShowDoorStatusCommand(door);
		
		controls.setCommand(showStatus);
		controls.pressCommand();
        
		controls.setCommand(lockDoor);
		controls.pressCommand();
		
        //Door won't open because it's locked 
		controls.setCommand(openDoor);
		controls.pressCommand();
		
		controls.setCommand(showStatus);
		controls.pressCommand();
		
		controls.setCommand(unlockDoor);
		controls.pressCommand();
		
		controls.setCommand(openDoor);
		controls.pressCommand();
		
		controls.setCommand(showStatus);
		controls.pressCommand();
		
		controls.setCommand(closeDoor);
		controls.pressCommand();
		
		controls.setCommand(showStatus);
		controls.pressCommand();
	}
}

The invoker is the class that the client will use to invoke all the requests.

The client is mainly to simulate the application.

//output
The door is currently closed and is unlocked
The door is currently closed and is locked
The door is currently open and is unlocked
The door is currently closed and is unlocked

The output shown represents the commands that were taken in order of execution.

When to use

The command pattern is particularly used in certain situtions mentioned below:

  • The commands need to have a lifespan in order to undo/redo such an action or to queue the requests to execute in a later time.
  • To provide a structured interface for low level operations.

Advantages

  • Able to track commands and able to redo/undo an action.
  • Simple code that is easy to understand and debug
  • Able to queue up the commands to allow a sequence of commands.
  • Decouples the the class that performs the action with the class that invokes the actions (the class that does the behind the scenes actions with the class that requests an action to be performed).
  • Easy to extend new commands and change the inner details without changing much existing code.

Disadvantages

  • Each command is a concrete class
  • There can be a lot of concrete classes.

Real World Applications

The command pattern is used quite often in many domains that are not listed but here are a few realistic real world applications of the command pattern being used.

  • Graphical User Interface (GUI): Buttons for example can provide an action when it's clicked or perhaps when it's held down.
  • Transactions: There are cases where actions must be rolled back such as cancelling a purchase or returning a purchase to get your money back.
  • Video Games: Many games provide a feature to undo previous actions/commands.
  • To implement callbacks

With this article at OpenGenus, you must have the complete idea of Command Pattern in Java. Enjoy.