0% found this document useful (0 votes)
3 views

06 - Command Pattern

Uploaded by

buseccoban
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

06 - Command Pattern

Uploaded by

buseccoban
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

CSE351 DESIGN 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
Cubicle conversation

Mary: Yes, I thought we'd see a bunch of classes with on() and off() methods, but here we've got
methods like dim(), setTemperature(), setVolume(), setDirection().
Sue: Not only that, it sounds like we can expect more vendor classes in the future with just as diverse
methods.
Mary: I think it's important we view this as a separation of concerns: the remote should know how to
interpret button presses and make requests, but it shouldn't know a lot about home automation or how to
turn on a hot tub.
Sue: Sounds like good design. But if the remote is dumb and just knows how to make generic
requests, how do we design the remote so that it can invoke an action that, say, turns on a light or
opens a garage door?
Mary: I'm not sure, but we don't want the remote to have to know the specifics of the vendor classes.
Sue: What do you mean?
Mary: We don't want the remote to consist of a set of if statements, like "if slot1 == Light, then
light.on(), else if slot1 = Hottub then hottub.jetsOn()". We know that is a bad design.
Sue: I agree. Whenever a new vendor class comes out, we'd have to go in and modify the code, potentially creating
bugs and more work for ourselves!
4
Cubicle conversation
Mary: Yeah? Tell us more.
Joe: The Command Pattern allows you to decouple the requester of an action from the
object that actually performs the action. So, here the requester would be the remote control and
the object that performs the action would be an instance of one of your vendor classes.
Sue: How is that possible? How can we decouple them? After all, when I press a button, the remote has to turn on a light.
Joe: You can do that by introducing "command objects" into your design. A command object encapsulates a request to do something
(like turn on a light) on a specific object (say, the living room light object). So, if we store a command object for each button, when
the button is pressed we ask the command object to do some work. The remote doesn't have any idea what the work is, it just has a
command object that knows how to talk to the right object to get the work done. So, you see, the remote is decoupled from the
light object!
Sue: This certainly sounds like it's going in the right direction.
Mary: Still, I'm having a hard time wrapping my head around the pattern.
Joe: Given that the objects are so decoupled, it's a little difficult to picture how the pattern actually works.
Mary: Let me see if I at least have the right idea: using this pattern, we could create an API in which these command objects can be loaded into
button slots, allowing the remote code to stay very simple. And, the command objects encapsulate how to do a home automation task along with
the object that needs to do it.
Joe: Yes, I think so. I also think this pattern can help you with that Undo button, but I haven't studied that part yet.
Mary: This sounds really encouraging, but I think I have a bit of work to do to really "get" the pattern.
Sue: Me too.
5
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
there are no
Dumb Questions
Q: Do I always need a receiver? Why can't the command Q: Could I have just implemented Party Mode as a Command by
object implement the details of the execute() method? creating a PartyCommand and putting the calls to execute the
other Commands in the PartyCommand's execute() method?
A: In general, we strive for "dumb" command objects that just
invoke an action on a receiver; however, there are many A: You could; however, you'd essentially be "hardcoding" the
examples of "smart" command objects that implement most, party mode into the PartyCommand. Why go to the trouble?
if not all, of the logic needed to carry out a request. With the MacroCommand, you can decide dynamically which
Certainly you can do this; just keep in mind you'll no longer Commands you want to go into the PartyCommand, so you
have the same level of decoupling between the invoker and have more flexibility using MacroCommands. In general, the
receiver, nor will you be able to parameterize your commands MacroCommand is a more elegant solution and requires less
with receivers. new code.
Q: How can I implement a history of undo operations? In
other words? I want to be able to press the undo button
multiple times.
A: Great question! It's pretty easy actually; instead of
keeping just a reference to the last Command executed, you
keep a stack of previous commands. Then, whenever undo is
pressed, your invoker pops the first item off the stack and
calls its undo() method.

33
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