70. Command Pattern and how it should be used..

What it is:

A way of decoupling a client object (aka the ‘sender’ of a request/command) from the object (aka the ‘receiver’) which receives the request/command to execute an operation .

Why do it:

It allows the details of how a request/command is executed to be hidden from the client.

Important:

It’s important to note that the ‘request/command’ is NOT a method on the ‘receiver’ object, but rather it is an object itself. In Java, you would create a Command interface (containing an execute() method) from which you would implement your commands.

How is it done:

Objects:

  1. Client/Sender
  2. Invoker
  3. Command/Request
  4. Receiver

The Client/Sender has no knowledge of the Receiver’s interface, instead the Client interacts with another class (aka the ‘Invoker’) which will maintain a list/queue/etc of Request/Command objects and will apply ‘command.execute()’ to the appropriate command as instructed by the client. Command.execute() will then interact with the Receiver’s interface.

The Command pattern turns ‘requests’ which are typically methods of a class into objects of their own. You may have a ‘Command’ interface and subclasses of SelectCommand or PlayCommand in a language such as Java or make use of mixins or simply duck typing in Ruby.

Sequence Diagram of the flow:

→ aClient creates aCommand and ‘stores’ it in anInvoker.

→ aClient asks anInvoker to execute a aCommand

→ anInvoker sends aCommand.execute() request which sends aReceiver.action() request.

An Example From the Web : A Light Switching Application

The components:

Client/SenderApplication

Invoker → the ‘Light Switch

Command/Request → Command<I> and it’s implementations: LightOnCommand, LightOffCommand

ReceiverLight

Sequence of flow:

client →Switch.new(LightOnCommand.new(aLight), LightOffCommand.new(aLight))

client->Switch.flipUp() →onCommand.execute() →aLight.turnOn()

TestCommand.java
class Fan {
public void startRotate() {
System.out.println("Fan is rotating");
}
public void stopRotate() {
System.out.println("Fan is not rotating");
}
}
class Light {
public void turnOn( ) {
System.out.println("Light is on ");
}
public void turnOff( ) {
System.out.println("Light is off");
}
}
class Switch {
private Command UpCommand, DownCommand;
public Switch( Command Up, Command Down) {
UpCommand = Up; // concrete Command registers itself with the invoker
DownCommand = Down;
}
void flipUp( ) { // invoker calls back concrete Command, which executes the Command on the receiver
UpCommand . execute ( ) ;
}
void flipDown( ) {
DownCommand . execute ( );
}
}
class LightOnCommand implements Command {
private Light myLight;
public LightOnCommand ( Light L) {
myLight = L;
}
public void execute( ) {
myLight . turnOn( );
}
}
class LightOffCommand implements Command {
private Light myLight;
public LightOffCommand ( Light L) {
myLight = L;
}
public void execute( ) {
myLight . turnOff( );
}
}
class FanOnCommand implements Command {
private Fan myFan;
public FanOnCommand ( Fan F) {
myFan = F;
}
public void execute( ) {
myFan . startRotate( );
}
}
class FanOffCommand implements Command {
private Fan myFan;
public FanOffCommand ( Fan F) {
myFan = F;
}
public void execute( ) {
myFan . stopRotate( );
}
}
public class TestCommand {
public static void main(String[] args) {
Light testLight = new Light( );
LightOnCommand testLOC = new LightOnCommand(testLight);
LightOffCommand testLFC = new LightOffCommand(testLight);
Switch testSwitch = new Switch( testLOC,testLFC);
testSwitch.flipUp( );
testSwitch.flipDown( );
Fan testFan = new Fan( );
FanOnCommand foc = new FanOnCommand(testFan);
FanOffCommand ffc = new FanOffCommand(testFan);
Switch ts = new Switch( foc,ffc);
ts.flipUp( );
ts.flipDown( );
}
}
Command.java
public interface Command {
public abstract void execute ( );
}

What I Am Doing in my Sinatra Web UI (and How I Could Change it to better fit the Command Pattern):

This is from my Client/Web_Application:

get ‘/game/play’ do
@display = WebDisplay.new
web_game_create = WebGameCreate.new(params, WebPlayerFactory.new(display), display)
web_game_create.execute
WebGamePlay.new(params, web_game_create).play
@presenter = WebPresenter.new(web_game_create)
....
end

Right away, you should see I don’t have an Invoker class. I have two Command-like objects: WebCreateGame and WebPlayGame. Also, WebPresenter could also be another command e.g. WebGamePresent

If I followed the Command pattern to the letter then I think what it should look like is:

get '/game/play' do
@display = WebDisplay.new

# COMMANDS:
create_command = WebGameCreate.new(params, WebPlayerFactory.new(display), display)
play_command = WebGamePlay.new(params)
present_command = WebGamePresent.new
# INVOKER:
game_command_broker = GameCommandBroker.new(create_command, play_command, present_command)

# SEND COMMAND TO RECEIVER
receiver_game = gameCommandBroker.create_game
game_command_broker.play_game(receiver_game)
@game_results = game_command_broker.present_game(receiver_game)
...
end
# GameCommandBroker
class GameCommandBroker
  def initialize(create_command, play_command, present_command)
@create_command = create_command
@play_command = play_command
@present_command = present_command
end
  def create_game
@create_command.execute
end
  def play_game(receiver_game)
@play_command.execute(receiver_game)
end
  def present_game(receiver_game)
@present_command.execute(receiver_game)
end
end

Problem with this design:

  • There is a lot of code in the Sinatra Web Application which is difficult to test.

→ Potential solution which doesn’t conform to the Command pattern as laid out above (to be reviewed by mentors!!), is below

  • I’m not entirely sure WebPresenter to WebGamePresent is a good move as it isn’t performing an ‘action’ as such, rather it is more of a data store.
get '/game/play' do
@display = WebDisplay.new
  game_conduit = GameConduit.new(create_command, play_command, present_command, game_command_broker) 
@game_results = game_conduit.play
  ...
end
class GameConduit
def initialize(create_command, play_command, present_command, game_command_broker)
@create_command = create_command
@play_command = play_command
@present_command = present_command
@game_command_broker = game_command_broker
end
  def create
receiver_game = gameCommandBroker.create_game
game_command_broker.present_game(receiver_game)
end
  def play
receiver_game = gameCommandBroker.create_game
game_command_broker.play_game(receiver_game)
game_command_broker.present_game(receiver_game)
end
end
Show your support

Clapping shows how much you appreciated sarah johnston’s story.