0% found this document useful (0 votes)
4 views33 pages

06 - Command Pattern

The document explains the Command Pattern in object-oriented design, illustrating how it encapsulates requests as command objects that can be executed without needing to know the details of the receiver. It uses a diner analogy to demonstrate the separation of request-making and request-executing objects, and provides code examples for implementing a remote control system with commands for various devices. Additionally, it discusses enhancements such as undo functionality and macro commands for executing multiple commands simultaneously.

Uploaded by

Batuhan Ekinci
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views33 pages

06 - Command Pattern

The document explains the Command Pattern in object-oriented design, illustrating how it encapsulates requests as command objects that can be executed without needing to know the details of the receiver. It uses a diner analogy to demonstrate the separation of request-making and request-executing objects, and provides code examples for implementing a remote control system with commands for various devices. Additionally, it discusses enhancements such as undo functionality and macro commands for executing multiple commands simultaneously.

Uploaded by

Batuhan Ekinci
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 33

BIM492DESIGN PATTERNS

06. COMMAND PATTERN

Encapsulating Invocation
Taking encapsulation whole new level
We did well at Weather-O-Rama!
Free hardware! Let's check out the Remote Control…

2
Taking a look at the vendor classes
 We have quite a set of classes here
 Sounds like we can expect more

3
Meanwhile, back at the Diner…,
or, A brief introduction to the Command Pattern
Let's take a short detour back to the diner and study the interactions
between the customers, the waitress, and the short-order cook.

6
Let's study the interaction
in a little more detail…

7
The Objectville Diner roles and responsibilities
An order slip encapsulates a request to prepare a meal.
 Think of the Order Slip as an object, an object that acts as a request to
prepare a meal.
 it can be passed around
 has an interface of only one method, orderUp()
 has a reference to the object that will do the job (in our case, the Cook)
 waitress doesn't need to know what's in the order or who will cook it

The waitress's job is to take Order Slip and invoke the orderUp()
method.

The short-order cook has the knowledge required to prepare the


meal.
 The object that really knows how to prepare meal.
 Gets the instructions from the Order Slip, never needs to directly
communicate with waitress

8
The Diner is a model for the Command Pattern

Patience, we're getting there...


Think of the Diner as a model for an 00 design pattern
that allows us to separate an object making a request
from the objects that receive and execute those
requests.

For instance, in our remote control API, we need to separate the code that gets invoked
when we press a button from the objects of the vendor-specific classes that carry out
those requests.

9
From the Diner to
the Command Pattern

10
Who does what?
Match the diner objects and methods with corresponding names from the Command Pattern.

Diner Command Pattern


Waitress Command
Short Order Cook execute()
orderUp() Client
Order Invoker
Customer Receiver
takeOrder() setCommand()

11
Our first command object
Let's build a few things from the bottom up…
Implementing the Command interface
public interface Command {
public void execute();
}
Implementing a Command to turn a light on
 now, let's say you want to implement a command for turning a light on
 the light class has two methods: on() and off()
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {


this.light = light;
}

public void execute() {


light.on();
}
} 12
OK , let's keep it simple: we've got a remote with only one button
and corresponding slot to hold a device control
Using the
public class SimpleRemoteControl {
command object
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}

public class RemoteControlTest {


public static void main(String args[]) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand();
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
} 13
The Command Pattern defined
 A command object encapsulates a request by binding
together a set of actions on a specific receiver.
 It packages the actions and the receiver up into an
object that exposes just one method, execute().
 When called, execute()causes the actions to be
invoked on the receiver.
 From the outside, no other objects really know what
actions get performed on what receiver; they just
know that if they call the execute() method, their
request will be serviced.
 What we haven't encountered yet is using commands
to implement queues and logs and support undo
operations.
 We can also easily support what's known as the
Meta Command Pattern once we have the basics in
place.
 The Meta Command Pattern allows you to create
macros of commands so that you can execute
multiple commands at once.

14
Class diagram

15
Assigning Commands
to slots
So, we have a plan:
 We're going to assign each slot to a command
in the remote control.
 This makes the remote control our invoker.
 When a button is pressed the execute()
method is going to be called on the
corresponding command, which results in
actions being invoked on the receiver (like
lights, ceiling fans, stereos).

16
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Implementing the
public RemoteControl() {
onCommands = new Command[7];
Remote Control
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() +
" " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.toString();
}
} 17
public class LightOffCommand implements Command {
Light light; Implementing the Commands
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
}

public class StereoOnWithCDCommand implements Command {


Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}

18
public class RemoteLoader { Putting the Remote Control
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
through its paces
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
CeilingFan ceilingFan= new CeilingFan("Living Room");
GarageDoor garageDoor = new GarageDoor("");
Stereo stereo = new Stereo("Living Room");

LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);


LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);

CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);


CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor);


GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor);

StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);


StereoOffCommand stereoOff = new StereoOffCommand(stereo);

19
Putting the Remote Control through its paces
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
remoteControl.setCommand(3, stereoOnWithCD, stereoOff);

System.out.println(remoteControl);

remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.onButtonWasPushed(3);
remoteControl.offButtonWasPushed(3);
}
}

20
Now, let's check out the execution of remote control test…

21
Good catch. null object
In the remote control, we didn't want to check to see if a command
was loaded every time we referenced a slot. For instance, in the
onButtonWasPushed() method, we would need code like this:
public void onButtonWasPushed(int slot) {
if (onCommands[slot] != null) {
onCommands[slot].execute();
}
}

So, how do we get around that? Implement a command that does nothing!
public class NoCommand implements Command {
public void execute() { }
}

Then, in our RemoteControl constructor, we assign every slot a NoCommand object by default and we know
we'll always have some command to call in each slot. Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
22
null object

The NoCommand object is an example of a null object.

A null object is useful when you don't have a meaningful object to return, and
yet you want to remove the responsibility for handling null from the client.

For instance, in our remote control we didn't have a meaningful object to


assign to each slot out of the box, so we provided a NoCommand object that
acts as a surrogate and does nothing when its execute method is called.
You'll find uses for Null Objects in conjunction with many Design Patterns
and sometimes you'll even see Null Object listed as a Design Pattern.

23
Time to write
that documentation

24
What are we doing?
We forgot something!
Okay, we need to add functionality to support the undo button on the remote.
It works like this: say the Living Room Light is off and you press the on
button on the remote. Obviously the light turns on. Now if you press the undo
button then the last action will be reversed — in this case the light will turn
off. Before we get into more complex examples, let's get the light working with Oops! We almost forgot... luckily, once
we have our basic Command classes,
the undo button: undo is easy to add. Let's step through
adding undo to our commands and to
1. When commands support undo, they have an undo() method that mirrors the the remote control...
execute() method. Whatever execute() last did, undo() reverses. So, before
we can add undo to our commands, we need to add an undo() method to the
Command interface:
public interface Command {
public void execute();
public void undo();
}

That was simple enough.


Now, let's dive into the Light command and implement undo() method.

25
What are we doing? undo
2. Let's start with the LightOnCommand: if the LightOnCommand's execute()
method was called, then the on() method was last called. We know that undo()
needs to do the opposite of this by calling the off() method.
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) { Piece of cake! Now for the LightOffCommand. Here the undo()
this.light = light;
method just needs to call the Light's on() method.
}
public void execute() { public class LightOffCommand implements Command {
light.on(); Light light;
} public LightOffCommand(Light light) {
public void undo() { this.light = light;
light.off(); }
} public void execute() {
} light.off();
}
Could this be any easier? Okay, we aren't done yet; we need public void undo() {
to work a little support into the Remote Control to handle light.on();
tracking the last button pressed and the undo button }
}
press.
26
public class RemoteControlWithUndo {
Command[] onCommands;
Command[] offCommands;
implementing undo
Command undoCommand;
public RemoteControlWithUndo() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i=0;i<7;i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed() {
undoCommand.undo();
}
public String toString() {
// toString code here… } } 27
public class CeilingFan {
public static final int HIGH = 3;
public static final int MEDIUM = 2;
Using state to implement Undo
public static final int LOW = 1;
public static final int OFF = 0;
String location;
int speed;
public CeilingFan(String location) {
this.location = location; speed = OFF;
}
public void high() {
speed = HIGH; // code to set fan to high...
}
public void medium() {
speed = MEDIUM; // code to set fan to medium...
}
public void low() {
speed = LOW; // code to set fan to low...
}
public void off() {
speed = OFF; // code to set turn fan off...
}
public int getSpeed() {
return speed;
}
}
28
Adding Undo to the ceiling fan commands
public class CeilingFanHighCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed;
public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.high();
}
public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off(); Can you implement other ceiling fan commands:
}
} low, medium, and off?
} 29
Every remote needs Party Mode!
What's the point of having a remote if you can't
push one button and have the lights dimmed,
the stereo and TV turned on and set to a DVD
and the hot tub fired up?

public class MacroCommand implements Command {


Command[] commands;

public MacroCommand(Command[] commands) {


this.commands = commands;
}

public void execute() {


for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
}
30
Using a macro command
1. First we set the set of commands we want to go into the macro:
Light light = new Light("Living Room");
TV tv = new TV("Living Room");
Stereo stereo = new Stereo("Living Room");
Hottub hottub = new Hottub();

LightOnCommand lightOn = new LightOnCommand(light);


StereoOnCommand stereoOn = new StereoOnCommand(stereo);
TVOnCommand tvOn = new TVOnCommand(tv);
HottubOnCommand hottubOn = new HottubOnCommand(hottub);

2. Next we create two arrays, one for the On commands and one for
the Off commands, and load them with corresponding commands:
Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);

3. Then we assign MacroCommand to a button like we always do:


remoteControl.setCommand(0, partyOnMacro, partyOffMacro);

31
Using a macro command cont.
4. Finally, we just need to push some buttons and see if this works.
System.out.println(remoteControl);
System.out.println("--- Pushing Macro On---");
remoteControl.onButtonWasPushed(0);
System.out.println("--- Pushing Macro Off---");
remoteControl.offButtonWasPushed(0);

32
More uses of the Command
Pattern: queuing requests
Commands give us a way to package a piece of computation (a
receiver and a set of actions) and pass it around as a first-
class object. Now, the computation itself may be invoked long
after some client application creates the command object. In
fact, it may even be invoked by a different thread. We can take
this scenario and apply it to many useful applications such
as schedulers, thread pools and job queues, to name a few.

Imagine a job queue: you add commands


to the queue on one end, and on the
other end sit a group of threads.
Threads run the following script: they
remove a command from the queue, call
its execute() method, wait for the call
to finish, then discard the command
object and retrieve a new one.
34
More uses of the Command Pattern: logging requests
The semantics of some applications require that we log all actions and be able
to recover after a crash by reinvoking those actions. The Command Pattern
can support these semantics with the addition of two methods: store() and
load().

35
Tools for your Design Toolbox
Bullet Points
 The Command Pattern decouples an object, making a request
from the one that knows how to perform it.
 A Command object is at the center of this decoupling and
encapsulates a receiver with an action (or set of actions).
 An invoker makes a request of a Command object by calling its
execute() method, which invokes those actions on the receiver.
 Invokers can be parameterized with Commands, even dynamically
at runtime. Commands may support undo operation.
 Macro Commands are a simple extension of Command that allow
multiple commands to be invoked. Likewise, Macro Commands
can easily support undo().
 In practice, it is not uncommon for "smart" Command objects
to implement the request themselves rather than delegating to
a receiver.
 Commands may also be used to implement logging.
36

You might also like