Command Design Pattern
overview
DEFINITION
-
IS A BEHAVIORAL DESIGN PATTERN
-
turns a request into a stand-alone object that contains all information about the request
-
parameterize methods with different requests, delay or queue a request’s execution
-
support undoable operations
PLAIN WORDS
Allows you to encapsulate actions in objects. The key idea behind this pattern is to provide the means to decouple client from receiver.
REAL WORLD EXAMPLE
ordering food at a restaurant:
You (i.e. Client) ask the waiter (i.e. Invoker) to bring some food (i.e. Command) and waiter simply forwards the request to Chef (i.e. Receiver) who has the knowledge of what and how to cook.
The Problem
- A text editor app with a bunch of buttons
- Can be for various different operations
- The buttons may inherit the Button parent class
- Some buttons may share the same code & business logic
The Problem
Business Logic & User Interface Layer is tightly coupled together
The Problem
Some operations may have different parameters
The Problem
Some operations can be invoked from different places
The SOLUTION
Structure
Structure explaination
CLass Diagram
Pseudocode
// The base command class defines the common interface for all
// concrete commands.
abstract class Command is
protected field app: Application
protected field editor: Editor
protected field backup: text
constructor Command(app: Application, editor: Editor) is
this.app = app
this.editor = editor
// Make a backup of the editor's state.
method saveBackup() is
backup = editor.text
// Restore the editor's state.
method undo() is
editor.text = backup
// The execution method is declared abstract to force all
// concrete commands to provide their own implementations.
// The method must return true or false depending on whether
// the command changes the editor's state.
abstract method execute()
// The concrete commands go here.
class CopyCommand extends Command is
// The copy command isn't saved to the history since it
// doesn't change the editor's state.
method execute() is
app.clipboard = editor.getSelection()
return false
class CutCommand extends Command is
// The cut command does change the editor's state, therefore
// it must be saved to the history. And it'll be saved as
// long as the method returns true.
method execute() is
saveBackup()
app.clipboard = editor.getSelection()
editor.deleteSelection()
return true
class PasteCommand extends Command is
method execute() is
saveBackup()
editor.replaceSelection(app.clipboard)
return true
// The undo operation is also a command.
class UndoCommand extends Command is
method execute() is
app.undo()
return false
// The global command history is just a stack.
class CommandHistory is
private field history: array of Command
// Last in...
method push(c: Command) is
// Push the command to the end of the history array.
// ...first out
method pop():Command is
// Get the most recent command from the history.
// The editor class has actual text editing operations. It plays
// the role of a receiver: all commands end up delegating
// execution to the editor's methods.
class Editor is
field text: string
method getSelection() is
// Return selected text.
method deleteSelection() is
// Delete selected text.
method replaceSelection(text) is
// Insert the clipboard's contents at the current
// position.
// The application class sets up object relations. It acts as a
// sender: when something needs to be done, it creates a command
// object and executes it.
class Application is
field clipboard: string
field editors: array of Editors
field activeEditor: Editor
field history: CommandHistory
// The code which assigns commands to UI objects may look
// like this.
method createUI() is
// ...
copy = function() { executeCommand(
new CopyCommand(this, activeEditor)) }
copyButton.setCommand(copy)
shortcuts.onKeyPress("Ctrl+C", copy)
cut = function() { executeCommand(
new CutCommand(this, activeEditor)) }
cutButton.setCommand(cut)
shortcuts.onKeyPress("Ctrl+X", cut)
paste = function() { executeCommand(
new PasteCommand(this, activeEditor)) }
pasteButton.setCommand(paste)
shortcuts.onKeyPress("Ctrl+V", paste)
undo = function() { executeCommand(
new UndoCommand(this, activeEditor)) }
undoButton.setCommand(undo)
shortcuts.onKeyPress("Ctrl+Z", undo)
// Execute a command and check whether it has to be added to
// the history.
method executeCommand(command) is
if (command.execute)
history.push(command)
// Take the most recent command from the history and run its
// undo method. Note that we don't know the class of that
// command. But we don't have to, since the command knows
// how to undo its own action.
method undo() is
command = history.pop()
if (command != null)
command.undo()
Application
WHen to use the command pattern
When you want to parametrize objects with operations.
lets say We want to program a remote control api
but we have so many diff vendor classes with diff operations
with command pattern, we encapsulate each operation into a command
Application
WHen to use the command pattern
You want to queue operations, schedule their execution or execute them remotely.
job queue
Receiver
Invoker
Commands
job queue: The Nodes
// READER
export const ftpReader = new EtlNode(
1,
'Extract From FTP',
new FtpReader({
host: 'localhost',
port: 22,
username: 'wmhafiz',
password: 'mysuperpassword',
sourcePath: '/home/source_path',
destPath: '/home/dest_path',
})
);
const ftpReaderOutput = ftpReader.run();
// UPPER CASE
export const upperCaseNode = new EtlNode(
2,
'Uppercase Company Name',
new UpperCaseTransformer({
destField: 'companyName_upper',
sourceField: 'companyName',
}),
ftpReaderOutput
);
const upperCaseOutput = upperCaseNode.run();
// HARMONIZE
export const harmonizeNode = new EtlNode(
3,
'Harmonize Company Name',
new HarmonizeTransformer({
destField: 'companyName_std',
dict,
sourceField: 'companyName_upper',
}),
upperCaseOutput
);
const finalOutput = harmonizeNode.run();
const dict = {
entries: [
{
key1: 'Sendirian',
key2: 'SDN',
method: TextReplacerMethod.WORD,
},
{
key1: 'SND',
key2: 'SDN',
method: TextReplacerMethod.WORD,
},
],
};
JOB QUEUE: The Client
import { harmonizeNode, upperCaseNode, ftpReader } from './node.test';
import { EtlController } from './controller';
export const controller = new EtlController(1, 'Simple ETL Job');
controller.addNode(ftpReader);
controller.addNode(upperCaseNode);
controller.addNode(harmonizeNode);
controller.addRelationship({
id: 1,
title: 'Ftp Output',
sourceNode: ftpReader,
destNode: upperCaseNode,
});
controller.addRelationship({
id: 2,
title: 'Uppercase Output',
sourceNode: upperCaseNode,
destNode: harmonizeNode,
});
// operations
controller.start();
controller.pause();
controller.resume();
Application
WHen to use the command pattern
You want to implement reversible operations.
IMPLEMENTING UNDO
IMPLEMENTING UNDO
logging / auto failover
- Store a backup on every command execution
- Load the backup upon any server crashing to restore the state
references
Kahoot
Command Design Pattern
By Wan Mohd Hafiz
Command Design Pattern
- 368