Command
Also known as¶
- Action
- Transaction
Intent¶
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Explanation¶
Real-world example
Imagine a smart home system where you can control devices such as lights, thermostats, and security cameras through a central application. Each command to operate these devices is encapsulated as an object, enabling the system to queue, execute sequentially, and undo commands if necessary. This approach decouples control logic from device implementation, allowing easy addition of new devices or features without altering the core application.
In plain words
Storing requests as command objects allows performing an action or undoing it at a later time.
Wikipedia says
In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time.
Sequence diagram
sequenceDiagram
participant Client
participant Wizard
participant Goblin
Client->>Wizard: castSpell(goblin::changeSize)
Wizard->>Goblin: changeSize()
Wizard->>Wizard: push to undoStack
Client->>Wizard: undoLastSpell()
Wizard->>Wizard: pop from undoStack
Wizard->>Goblin: changeSize()
Wizard->>Wizard: push to redoStack
Client->>Wizard: redoLastSpell()
Wizard->>Wizard: pop from redoStack
Wizard->>Goblin: changeSize()
Wizard->>Wizard: push to undoStack
Programmatic Example¶
In the Command pattern, objects are used to encapsulate all information needed to perform an action or trigger an event at a later time. This pattern is particularly useful for implementing undo functionality in applications.
In our example, a Wizard casts spells on a Goblin. Each
spell is a command object that can be executed and undone. The
spells are executed on the goblin one by one. The first spell
shrinks the goblin and the second makes him invisible. Then the
wizard reverses the spells one by one.
First, we have the Size and Visibility enumerations that
define the target's state.
internal enum class Size {
NORMAL,
SMALL,
;
override fun toString() = name.lowercase()
}
internal enum class Visibility {
INVISIBLE,
VISIBLE,
;
override fun toString() = name.lowercase()
}
Target is the abstract base class. A target has a Size and
a Visibility that can be toggled.
internal abstract class Target(
var size: Size,
var visibility: Visibility,
) {
fun printStatus() {
logger.info("$this, [size=$size] [visibility=$visibility]")
}
fun changeSize() {
size =
if (size == Size.NORMAL) Size.SMALL else Size.NORMAL
}
fun changeVisibility() {
visibility =
if (visibility == Visibility.INVISIBLE) {
Visibility.VISIBLE
} else {
Visibility.INVISIBLE
}
}
}
Goblin is the concrete target.
internal class Goblin : Target(
size = Size.NORMAL,
visibility = Visibility.VISIBLE,
) {
override fun toString() = "Goblin"
}
Wizard is the invoker. Commands are represented as simple
() -> Unit lambdas. The wizard keeps an undo stack and a redo
stack so that spells can be reversed and replayed.
internal class Wizard {
private val undoStack = ArrayDeque<() -> Unit>()
private val redoStack = ArrayDeque<() -> Unit>()
fun castSpell(spell: () -> Unit) {
spell()
undoStack.addLast(spell)
}
fun undoLastSpell() {
if (undoStack.isNotEmpty()) {
val previousSpell = undoStack.removeLast()
redoStack.addLast(previousSpell)
previousSpell()
}
}
fun redoLastSpell() {
if (redoStack.isNotEmpty()) {
val previousSpell = redoStack.removeLast()
undoStack.addLast(previousSpell)
previousSpell()
}
}
override fun toString() = "Wizard"
}
Here is the full example of the wizard casting spells.
fun main() {
val wizard = Wizard()
val goblin = Goblin()
goblin.printStatus()
wizard.castSpell(goblin::changeSize)
goblin.printStatus()
wizard.castSpell(goblin::changeVisibility)
goblin.printStatus()
wizard.undoLastSpell()
goblin.printStatus()
wizard.undoLastSpell()
goblin.printStatus()
wizard.redoLastSpell()
goblin.printStatus()
wizard.redoLastSpell()
goblin.printStatus()
}
Program output:
Goblin, [size=normal] [visibility=visible]
Goblin, [size=small] [visibility=visible]
Goblin, [size=small] [visibility=invisible]
Goblin, [size=small] [visibility=visible]
Goblin, [size=normal] [visibility=visible]
Goblin, [size=small] [visibility=visible]
Goblin, [size=small] [visibility=invisible]
Class diagram¶
classDiagram
class Target {
<<abstract>>
+size: Size
+visibility: Visibility
+printStatus()
+changeSize()
+changeVisibility()
}
class Goblin {
+toString(): String
}
class Wizard {
-undoStack: ArrayDeque~() -~ Unit~
-redoStack: ArrayDeque~() -~ Unit~
+castSpell(spell: () -~ Unit)
+undoLastSpell()
+redoLastSpell()
+toString(): String
}
class Size {
<<enumeration>>
NORMAL
SMALL
+toString(): String
}
class Visibility {
<<enumeration>>
INVISIBLE
VISIBLE
+toString(): String
}
Target <|-- Goblin
Target --> Size
Target --> Visibility
Wizard ..> Target : invokes commands on
Applicability¶
Use the Command pattern when you want to:
- Parameterize objects with actions to perform, offering an object-oriented alternative to callbacks. Commands can be registered and executed later.
- Specify, queue, and execute requests at different times, allowing commands to exist independently of the original request.
- Support undo functionality, where the command's execute operation stores state and includes an un-execute operation to reverse previous actions.
- Log changes to reapply them after a system crash by adding load and store operations to the command interface.
- Structure a system around high-level operations built on primitive operations, which is common in transaction-based systems.
Consequences¶
Benefits:
- Decouples the object that invokes the operation from the one that knows how to perform it.
- It is easy to add new commands because you do not have to change existing classes.
- You can assemble a set of commands into a composite command.
Trade-offs:
- Increases the number of classes for each individual command.
- Can complicate the design by adding multiple layers between senders and receivers.