Allows you to encapsulate actions in objects. The key idea behind this pattern is to provide the means to decouple client from receiver.
Business Logic & User Interface Layer is tightly coupled together
Some operations may have different parameters
Some operations can be invoked from different places
// 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()
When you want to parametrize objects with operations.
You want to queue operations, schedule their execution or execute them remotely.
Receiver
Invoker
Commands
// 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,
},
],
};
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();
You want to implement reversible operations.