Easy patterns: Command
This article is created in continuation of easy patterns series description and presents a behavioral pattern named a Command which helps to parametrize clients with different requests and support undoable operations.
Please refer to the other pattern articles as well:
Creational patterns:
Structural patterns:
Behavioral patterns:
Command (this article)
The main essence
Sometimes it’s necessary to issue requests to objects without knowing anything about the operation being requested on the receiver of the request. For example some button section has a button. On click some request should be handled, but button section can’t explicitly implement the request, it should be done by application itself. We do not know the receiver of the request or the operations that will carry it out, only application is aware about that.
This pattern is also known either as an Action or a Transaction.
This pattern includes four main roles:
- Command — defines a binding between a Receiver object and an Action. Implements an
.execute
method by invoking the corresponding operation on Receiver. - Invoker — asks the Command to carry out the request.
- Receiver — knows how to perform the operations associtated with carrying out a request.
- Client — creates a Command object and sets it’s receiver
A few words about roles collaboration.
- The client creates a Command object and specifies it’s receiver.
- An Invoker object stores the Command object.
- An Invoker issues a request by calling
.execute
on the Command. - In case if Command
.execute
is undoable Command stores state for undoing the command prior to Invoking the.execute
. - The Command object invokes operations on its Receiver to carry out the request.
Commands are an object-oriented replacement for callbacks (functions that are registered somewhere to be called at some later point).
It’s possible to send such action to execute in some other independent place, for example a different process.
The Command .execute
operation can store state for reversing its effect inside. So, basically you’re getting not only execute logic encapsulating inside, but also an .undo
logic.
Also it’s possible to reapply the logic if the system crash appeared. It happens due to the support of logging. Keeping a persistent log of changes in the system is possible to supply that.
Example of use
In this example we will create a coffee machine. Also we will create an Invoker class with the name ControlPanel, which would invoke a specific command for us by calling it’s .execute
method. Each instance of a Command class accepts a CoffeeMachine instance to operate later with. Action class already knows everything it needs for execution. But execution itself is delayed till ControlPanel class decides to do something with that (user button press, etc).
I’ve created here the Command class with abstract methods just to show up the structure of some specific command (each specific command class extends from this Command class).
Profit
Command pattern decouples the object that invokes the operation from the one that knows how to perform it.
Commands usually are the first-class objects (they can be manipulated and extended like any other object).
It’s possible to assemble commands into a composite command (macro command). So it’s possible to execute a groups of command at the same time.
New commands are easy to add because you don’t have to change existing classes.
Weak places
Could be an overhead in cases when decoupling Invoker from the Receiver makes no sense in quite simple logic. In some cases the PubSub pattern just enough to execute callbacks based on events listening.
Conclusion
There are several related patterns that can be used along with a Command pattern:
- A Composite pattern can be used to make a Macro Commands from simple Command classes.
- To help with
.undo
implementation for rolling back the previous state if something goes wrong the Memento pattern can be used.
A Composite pattern helps to structure a system around hign-level operations built on primitives operations. Such structure is common in the systems that support transactions. Each transaction encapsulates a set of changes to data. Basically transactions can be done with a command classes. Commands have a common interface, letting you invoke all transactions the same way. This pattern makes it easy to extend the system with new transactions, just by implementing new command classes.
If you found this article helpful, please hit the 👏 button and feel free to comment below!