The blog for Design Patterns, Linux, HA and Myself!
In this section we’ll look into Command Design Pattern. While learning the Command design pattern we’ll try to implement a Facility Dashboard for a company’s Facility team. The initial design will have some problems that we’ll try to fix using the Command Design Pattern. Afterwards, we’ll look into how Quartz Scheduler is using it.
Requirements:
The wireframe tells that there would be blocks for each of the devices and inside those blocks there would be buttons to control the device.
You can already reckon that for all of this to work the dashboard requires integration of multiple types of devices.
Also, the APIs for these connected devices do not follow a regular pattern.
For example, the Cameras have a method named startRecording
that starts the recording while the Lights
have a method
method, turnOn
, to switch it on.
//The class for Camera Driver.
class Camera {
// This method starts recording.
public void startRecording() {
System.out.println("Recording started.");
}
//This method stops recording
public void stopRecording() {
System.out.println("Recording stopped.");
}
}
// The class for Light Driver.
class Light {
// This method switches the light on.
public void turnOn() {
System.out.println("the Light is on now");
}
// This method switches the light off.
public void turnOff() {
System.out.println("the Light is off now");
}
}
From the first look the solution that we seems to involve if/else if cases based on the type of the button pressed:
if(pressedButton.equals(hallwayButton)){
light.turnOn();
} else if(pressedButton.equals(roofTopCameraButton)){
camera.startRecording();
}
If you’re following our previous tutorials then you would have already found out the problems that we might face while trying to find out the best solution. Let’s look into them.
In order to encapsulate the methods from the device we can create an interface named Command
with a method execute()
and then create multiple classes that implement the execute()
method by delegating the execution to the actual devices.
interface Command {
void execute();
void undo();
}
The following class implements the Command
interface and then delegates the execute()
method’s responsibility to
Camera
devices’s startRecording()
method.
class CameraStartRecordingCommand implements Command {
private Camera camera;
public CameraStartRecordingCommand(Camera camera) {
this.camera = camera;
}
@Override
public void execute() {
camera.startRecording();
}
@Override
public void undo() {
camera.stopRecording();
}
}
Note: Make sure to go through the code comments as well. It’ll help you understand the concept better.
And let the dashboard have these commands.
class DashboardScreen {
// the list of the commands
private List<Command> commands = new ArrayList<>();
// this attribute stores the command that was last executed.
// it is used for undo
private Command lastCommand;
public Command getCommand(int i) {
if (i > commands.size()) {
throw new NoSuchElementException(String.format("The dashboard screen has only %d commands", commands.size()));
}
return commands.get(i);
}
public void addCommand(Command command) {
commands.add(command);
}
public void setCommand(int i, Command command) {
commands.add(i, command);
}
public void executeCommand(int i) {
if (i > commands.size()) {
throw new NoSuchElementException(String.format("The dashboard screen has only %d commands", commands.size()));
}
commands.get(i).execute();
lastCommand = commands.get(i);
}
public void undo() {
lastCommand.undo();
}
}
So, now whenever a dashboard user presses any button, the dashboard can then call the execute method without actually knowing the receiver of method, the device. And the device doesn’t need to know if the caller is a dashboard user or any other user.
This is the Command Design Pattern.
Finally the definition from the Wikipedia
In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
You might be wondering about the differences between the Command Patterns and Observer pattern because sometime they appear to be similar. But there are differences:
Observer Pattern | Command Pattern |
---|---|
notifies zero to n interested parties that some event (for example a finished operation) happened | encapsulates a operation call in an object thus making it transferable over a wire or persist-able |
As we’ve some context about the Command Design Pattern now, let’s look into the code base of the Quartz Scheduler, an open source job scheduling library, from a very high level to checkout how they’ve used it. To execute a task as a cron, the user will have to pass the cron schedule and job that they want to run, and the quartz scheduler is expected to run the job according to the schedule.
The jobs can be of many types, like, making a HTTP request, writing to a file or executing SQL query. This way the job
arguments cannot remain static. So, how would the scheduler know which arguments are required by the job without any
tight coupling? This problem is very similar to the Light Bulb and the Camera problem that we’ve just solved. To solve
this problem, they’ve encapsulated the Job execution using an interface named Job
just like what we’ve done using
Command
interface.
This is how we can create a Job, similar to CameraStartRecordingCommand
, in Quartz.
class CameraStartRecordingJob implements Job {
private Camera camera;
public CameraStartRecordingJob(Camera camera) {
this.camera = camera;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
camera.startRecording();
}
}
Then it can be scheduled using the scheduler object:
JobDetail job = newJob(CameraStartRecordingJob.class)
.withIdentity("camera", "job")
.build();
sched.scheduleJob(job, newTrigger().withSchedule(cronSchedule("0 0 12 * * ?")).build());
This way the scheduler library is decoupled from the Camera device. It will just call the execute()
method, and it’s
upto the developer to properly delegate the request to the Camera.
I’ve created these tutorials after learning Design Patterns from this book Head First Design Patterns (A Brain Friendly Guide).