The Command Pattern encapsulates a request as an object,
thereby letting you parametrize other objects with different requests,
queue or log requests, and support undoable operations.
The Command Pattern decouples an object making a request, invoker, from the one that knows how to perform it, receiver.
A Command object implements the Command interface and is at the center of this decoupling. A concrete Command object 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 by implementing an undo() method that restores the object to its previous state before the execute() method was last called.
MacroCommands are a simple extension of Command that allow multiple commands to be invoked.
Likewise, MacroCommands 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 combine a receiver and its actions, which allows us to pass these packaged computations around and invoke them
at any time after creating the Command object. Clients that invoke such an object can be for example
scedulers, thread pools and job queus.
In a job queue commands are queued and then dequeud by threads which call the execute() method of the Command.
After the call finished the command object is discarded and a new one is processed.
This decouples the job queue classes from the objects that are doing teh computation.
Such an application is useful for web servers that handle requests from multiple users.
Commands may also be used to implement logging and transactional systems. Executed commands are stored in a history on disk. When a crash occurs, the command objects are reloaded and invoked calling their execute() methods in batch and in order. To achieve this, the Command interface requires store() and load() methods.
Such logging of command checkpoints is useful when working with large data, which would require time and space to store entire snapshots of the history, for example large text/spreadsheet documents or snapshots of virtual machines.
The following example is a remote control with multiple slots, where each slot has a corresponding on/off button.
Each button will have its own command object assigned. The command object knows which actions to call on its receiver.
Additionally, there is an undo button on the remote which can also be implemented as Command as we will see later.
The Command interface has one execute() method:
Concrete implementations of this interface for a light on/off command look like this:
For the on command:
And the off command:
Here the Light class is a receiver which can look like this:
A command with multiple actions looks like this:
The off command of the Stereo is similar to the light off command:
To use these command objects an invoker, in this example the remote control, needs to be configured to hold these commands in its slots,
which is implemented with two arrays of Command type, one for onCommands and one for offCommands. The last button press is stored
in the undoCommand member:
Note that the constructor of this RemoteControl class assigns NoCommand objects to the slots.
The NoCommand object implements the Command interface but its execute() and undo() methods do nothing and do not return anything.
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.
After having an option to assign commands to the invoker (remote), a client can set or programm the invoker:
Starting this application results in the following output.
Another example using the undo button is the following:
And its output:
Macro Command
It is also possible to combine multiple commands into one:
To create a macro the following steps need to be done:
Create the set of commands for the macro
Create two arrays, one for the On commands and one for the Off commands
Create new MacroCommand objects for the On and Off macros
Asign the MacroCommands to a button using the RemoteControl.setCommand() method.
Pushing a button that has a MacroCommand assigned will invoke the specified actions.
For the undo functionality of a MacroCommand all the commands that were invoked in the macro must undo their previous actions.
As shown in the previous code snippet, the commands need to be done backwards, to ensure proper undo functionality.
To implement a history of undo commands, in order to press the undo button multiple times, a stack of previous commands instead of just a reference to the last command is need. Then, whenever undo is pressed,
the invoker pops the first item (command) off the stack and calls its undo() method.
Lambda Expressions Implementation
To avoid having multiple small command classes that only have one method (execute()),
which provide a common interface to the behavior of many different receivers, we can use lambda expressions/functions instead.
Note that a lambda expression can only be used if its arguments and return type matches exactly one and only one method
in an interface. Lambda expressions are designed specifically to replace the methods inthese functional interfaces,
partly as a way to reduce the code that is required when you have a lot of these small classes with functional interfaces.
If the interface has two methods, it's not a functional interfae and it won't be possible to replace it with lambda expressions.
To achieve this we create a lambda expression, also called anonymous function, that is a function (or a subroutine) defined, and possibly called, without being bound to an identifier. In the example above, the lambda expression should call the
execute() method. To use lambda expressions the following steps are required:
Create the Receiver, which is the same as before
Set the remote control’s commands using lambda expressions
Instead of creating LightOnCommand and LightOffCommand objects to pass to the remoteControl.setCommand(), we simply pass a lambda expression in place of each object, with the code from their respective execute() method:
Push the remote control buttons
When we call the remote’s onButtonWasPushed(0) method, the command that’s in slot 0 is a function object (created by the lambda expression).
Because the lambda expression has the same signature as the execute() method of the Command interface, the compiler is able to match this method with the lambda expression.
Both have no arguments and no return types.
Therefore, calling onButtonWasPushed(0) invokes onCommands[0].execute() which the lambda expressions stands in for and its statements are executed.
Instead of using lambda expressions which call only one method, for example livingRoomLight.on();, it is possible to simplify the code using method references.
In case we need to call more than one method we need to create a lambda expression either in line,
or we can cwrite it separately, give it a name, and then pass this to the remoteControl’s setCommand() method.
For example with the sereoOnWithCDCommand that does three things:
a named lambda expressions, that has type Command to match the Command interface’s execute() method, would look like this:
This can then be passed using its name:
Testing the remote control with lambda expressions with the following main program:
The constructor of the RemoteControl can also be adapted to use lambda expressions instead of NoCommand objects to reduce the number of classes required.
Comments