What is Command Design Pattern ?
Command design pattern is a behavioral pattern in which object are used to encapsulate all the information that need to perform action or trigger an event at later time. This information includes the method name, the object that owns the method and values for the method parameters.
Intends
- Encapsulate a request in an object
- Allows the parameterization of clients with different requests
- Allows saving the requests in a queue
There are four terms associate with the command pattern. They are:
- Command: This element contains information about the necessary action to be taken. It calls the required method from receiver.
- Receiver: This element contains the actual implementation (knows how to carry out requested command) of any commands. This also maintains the history of executed commands.
- Invoker: This element initiates the whole process. It takes arguments from the client and invokes the process to call the required command.
-
Client: This element really behaves like a client (deciding what to do). Its job is to determine which command to execute, without knowing who will execute it and how it will be executed.
Example:
# Invoker
class Switch
attr_reader :history
def execute(cmd)
@history ||= []
@history << cmd.execute
end
end
# Command Interface
class Command
attr_reader :light
def initialize(light)
@light = light
end
def execute
raise NotImplementedError
end
End
# Command for turning on
class TurnOnCommand < Command
def execute
light.turn_on
end
end
# Command for turning off
class TurnOffCommand < Command
def execute
light.turn_off
end
end
# Receiver
class Light
def turn_on
'the light is on'
end
def turn_off
'the light is off'
end
end
# Client
class LightSwitchClient
attr_reader :switch
def initialize
@lamp = Light.new
@switch = Switch.new
end
def switch_for(cmd)
case cmd
when 'on' then @switch.execute(TurnOnCommand.new(@lamp))
when 'off' then @switch.execute(TurnOffCommand.new(@lamp))
else puts 'Sorry, I so sorry'
end
end
end
client = LightSwitchClient.new
puts client.switch_for('on')
puts client.switch_for('off')
puts client.switch.history
Output:
'the light is on'
'the light is off'
['the light is on', 'the light is off']
In above Example:
Switch class executing the commands and keeping the log of the commands. Command interface accepting the parameter is light in this case. We have two concrete command classes ( TurnOnCommand and TurnOffCommand ). These command classes invokes the methods of the receiver. Receiver does the actual works. Receiver’s job is to turn on the light or turn off the light. Client makes the decision which command should created and when to execute the commands.
Specific problems and implementation
Now that we have understood how the pattern works, it’s time to take a look at its advantages and flaws, too.
The intelligence of a command
There are two extremes that a programmer must avoid when using this pattern:
- The command is just a link between the receiver and the actions that carry out the request
- The command implements everything itself, without sending anything to the receiver.
We must always keep in mind the fact that the receiver is the one who knows how to perform the operations needed, the purpose of the command being to help the client to delegate its request quickly and to make sure the command ends up where it should.
Undo and redo actions
As mentioned above, some implementations of the Command design pattern include parts for supporting undo and redo of actions. In order to do that a mechanism to obtain past states of the Receiver object is needed; in order to achieve this there are two options:
Before running each command a snapshot of the receiver state is stored in memory. This does not require much programming effort but can not be always applied. For example doing this in an image processing application would require storing images in memory after each step, which is practically impossible.
Instead of storing receiver objects states, the set of performed operations are stored in memory. In this case the command and receiver classes should implement the inverse algorithms to undo each action. This will require additional programming effort, but less memory will be required. Sometimes for undo/redo actions the command should store more information about the state of the receiver objects. A good idea in such cases is to use the Memento Pattern.
Asynchronous Method Invocation
Another usage for the command design pattern is to run commands asynchronously in background of an application. The invoker runs in the main thread and sends the requests to the receiver which runs in a separate thread. The invoker will keep a queue of commands to be run and will send them to the receiver while it finishes running them.
Instead of using one thread in which the receiver is running more threads can be created for this. But for performance issues (thread creation is consuming) the number of threads should be limited. In this case the invoker will use a pool of receiver threads to run command asynchronously.
Using composite commands
When adding new commands to the application we can use the composite pattern to group existing commands in another new command. This way, macros can be created from existing commands.
Hot spot
The main advantage of the command design pattern is that it decouples the object that invokes the operation from the one that know how to perform it. And this advantage must be kept. There are implementations of this design pattern in which the invoker is aware of the concrete commands classes. This is wrong making the implementation more tightly coupled. The invoker should be aware only about the abstract command class.
Progress bars
Suppose a program has a sequence of commands that it executes in order. If each command object has a get_estimated_duration() method, the program can easily estimate the total duration. It can show a progress bar that meaningfully reflects how close the program is to completing all the tasks.
Macro recording
If all user actions are represented by command objects, a program can record a sequence of actions simply by keeping a list of the command objects as they are executed. It can then “play back” the same actions by executing the same command objects again in sequence. If the program embeds a scripting engine, each command object can implement a to_script() method, and user actions can then be easily recorded as scripts.
Related Articles
- Template Method Design Pattern in Rails
- How To Implement Law Of Demeter In Ruby
- Application Template in Rails
Final Words
Gurzu is a full-cycle Ruby on Rails development company. Since 2014, we have built software running on the Ruby on Rails framework for startups and enterprises from all around the world using Agile methodology. Our team of experienced Ruby developers can help to develop your next product.
Have a tech idea you want to turn into reality? Book a free consulting call.