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. This information includes the method name, the object that owns the method and values for the method parameters.
The command pattern is used to implement undoable operations, handle menu items in a graphical user interface (GUI) or implement network protocols. It is also a good choice for implementing macros or other high-level actions that should be able to be undone/redone.
The command pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
The command pattern is based on the principle of inversion of control. In traditional programming, the flow of control is determined by the structure of the code. In the command pattern, the flow of control is inverted: the object that invokes the method is not the same object that owns the method.
The command pattern has four main components:
The command pattern should be used when you want to parameterize objects with different requests, queue or log requests, and support undoable operations.
The main advantages of the command pattern are:
The main disadvantages of the command pattern are:
Let's see how the command pattern can be implemented in Kotlin.
The invoker is the object that invokes the command. It contains a reference to the command object.
class Invoker {
private val command: Command
constructor(command: Command) {
this.command = command
}
fun execute() {
command.execute()
}
}
The command interface is implemented by all command objects. It declares a method for executing the command.
interface Command {
fun execute()
}
The concrete command class implements the command interface. It contains a reference to the receiver object and defines the action that is executed when the execute method is invoked.
class ConcreteCommand(private val receiver: Receiver) : Command {
override fun execute() {
receiver.action()
}
}
The receiver is the object that owns the method that is invoked by the command.
class Receiver {
fun action() {
// ...
}
}
The command pattern can be used to implement undoable operations, handle menu items in a GUI or implement network protocols.
The command pattern is a good choice for implementing undoable operations. The undo method can be implemented by keeping a history of all the commands that have been executed.
class Invoker {
private val commands: Stack<Command> = Stack()
fun execute(command: Command) {
command.execute()
commands.push(command)
}
fun undo() {
if (!commands.isEmpty()) {
val command = commands.pop()
command.undo()
}
}
}
class ConcreteCommand(private val receiver: Receiver) : Command {
override fun execute() {
receiver.action()
}
override fun undo() {
receiver.undoAction()
}
}
class Receiver {
fun action() {
// ...
}
fun undoAction() {
// ...
}
}
The command pattern can be used to handle menu items in a GUI. In this example, the invoker is a menu and the receiver is a text editor.
class Menu(private val textEditor: TextEditor) {
private val commands: Stack<Command> = Stack()
fun clickUndo() {
if (!commands.isEmpty()) {
val command = commands.pop()
command.undo()
}
}
fun clickOpen() {
val command = OpenCommand(textEditor)
command.execute()
commands.push(command)
}
fun clickSave() {
val command = SaveCommand(textEditor)
command.execute()
commands.push(command)
}
}
class OpenCommand(private val textEditor: TextEditor) : Command {
override fun execute() {
textEditor.open()
}
override fun undo() {
textEditor.close()
}
}
class SaveCommand(private val textEditor: TextEditor) : Command {
override fun execute() {
textEditor.save()
}
override fun undo() {
// ...
}
}
class TextEditor {
fun open() {
// ...
}
fun save() {
// ...
}
fun close() {
// ...
}
}
The command pattern can be used to implement network protocols. In this example, the invoker is a client and the receiver is a server.
class Client {
private val commands: Stack<Command> = Stack()
fun send(command: Command) {
command.execute()
commands.push(command)
}
fun undo() {
if (!commands.isEmpty()) {
val command = commands.pop()
command.undo()
}
}
}
class Server {
fun action() {
// ...
}
fun undoAction() {
// ...
}
}
class ConcreteCommand(private val receiver: Receiver) : Command {
override fun execute() {
receiver.action()
}
override fun undo() {
receiver.undoAction()
}
}
In this blog post, we've learned about the command pattern and how it can be used to encapsulate requests as objects. We've also seen how the command pattern can be used to implement undoable operations, handle menu items in a GUI or implement network protocols.