
디자인 패턴 6. Command Pattern, 커맨드 패턴
디자인 패턴
커맨드 패턴이란?
요구사항을 객체로 캡슐화할 수 있으며,매개변수를 사용하여 여러 가지 다른 요구 사항을 집어넣을 수 있다. 요청 내역을 큐에 저장하거나 로그로 기록할 수 있고, 작업취소 기능도 지원한다.
- 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴
- 즉, 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고 재사용하고자 할 때 유용하다.
- 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자(Invoker) 클래스와 실제 기능을 실행하는 수신자(Receiver) 클래스 사이의 의존성을 제거한다.
- 따라서 실행될 기능의 변경에도 호출자 클래스를 수정 없이 그대로 사용 할 수 있도록 해준다.
구성요소

-
Client : ConcreteCommand를 생성하고 Reciver를 설정한다.
-
Invoker : 명령이 들어있고, exceute() 메서드를 호출함으로써 커맨드 객체에게 특정한 작업을 수행해 달라고 한다.
-
Command : 모든 커맨드 객체에서 구현해야 하는 인터페이스이다. 실행될 기능을 execute 메서드로 선언함
-
Reciver : 요구 사항을 수행하기 위해서 어떤 일을 처리해야 하는지 알고 있는 객체. ConcreteCommand에서 execute 메서드를 구현할 때 필요한 클래스 즉, ConcreteCommand의 기능을 실행하기 위해 사용하는 수신자 클래스
-
ConcreteCommand : 특정 행동과 리시버 사이를 연결해준다.
-
execute() : 메서드에는 리시버에 있는 메서드를 호출하여 요청된 작업을 수행한다.
예제
예제1 TypeScript

//Interface abstract class ICommand { abstract execute(args): void; } //Receiver class State { private _state: number; constructor(state) { this._state = state; } getState() { return this._state; } setState() { this._state = value; } } //Invorker class BankManager () { private _state; private _commands = {}; constructor() { this._state = state; } registerCommands(...args) { for (const cmd of args) { this._commands[cmd.constructor.name] = cmd; } } executeCmd(cmdName, param) { this._commands[cmdName].execute(this._state,param) } } //Command class Deposit implements ICommand { execute(...params):void { const [state, amount] = params; const prevState = state.getState() state.setState(prevState + amount) } } class Withdrawal implements ICommand { execute(...params):void { const [state, amount] = params; const prevState = state.getState() state.setState(prevState - amount) } } //1.command 생성 const deposit = new Deposit(); const withdrawal = new Withdrawal(); //2.receiver 생성 const state = new State(0); //3.invoker 인스턴스 & receiver 등록 const bankManager = new BankManager(); //4.command 등록 bankManager.registerCommands(deposit,withdrawal) //5.command 지시 bankManager.executeCmd('Deposit',100) bankManager.executeCmd('Deposit',1100) console.log(state.getState()); // 1100 bankManager.executeCmd('withdrawal',1000) console.log(state.getState()); // 100
예제 2
class Command { execute() {} } class PrintCommand extends Command { constructor(printStr) { this.printStr = printStr; } execute() { console.log(`from print command : ${this.printStr}`) } } const firstPrint = new PrintCommand('1') const secondPrint = new PrintCommand('2') firstPrint.execute() // from print command : 1 secondPrint.execute() // from print command : 2 class Dog { sit() { console.log('the dog sat down') } stay() { console.log('the dog is staying') } } class DogCommand extends Command { constructor (dog,commands) { this.dog = dog; this.commands = commands; } execute() { for (const command in this.commands) { if (command == 'sit ') this.dog.sit() if (command == 'stay ') this.dog.stay() } } } const baduk = new Dog(); const dogCommand = new DogCommand(baduk,['sit','stay','sit']) dogCommand.execute() // 'the dog sat down' // 'the dog is staying' // 'the dog sat down' class Invoker { constructor() { this.commandList = []; } addCommand(command) { this.commandList.push(command) } runCommand() { for (const command in this.commandList) { command.execute(); } } } const invoker = Invoker(); invoker.addCommand(firstPrint) invoker.addCommand(secondPrint) invoker.addCommand(dogCommand) invoker.runCommand(); // from print command : 1 // from print command : 2 // 'the dog sat down' // 'the dog is staying' // 'the dog sat down'
예제 3 취소기능
class Calculator { constructor() { this.value = 0; this.history = []; } executeCommand(command) { this.value = command.execute(this.value) this.history.push(command) } undo() { const command = this.history.pop() this.value = command.undo(this.value) } } class AddCommand { constructor(valueToAdd) { this.valueToAdd = valueToAdd } execute(currentValue) { return currentValue + this.valueToAdd } undo(currentValue) { return currentValue - this.valueToAdd } } class MultiplyCommand { constructor(valueToMultiply) { this.valueToMultiply = valueToMultiply } execute(currentValue) { return valueToMultiply * this.valueToMultiply } undo(currentValue) { return valueToMultiply / this.valueToMultiply } } const calculator = new Calculator(); calculator.executeCommand(new AddCommand(10)) calculator.executeCommand(new MultiplyCommand(2)) console.log(calculator.value) // 20 calculator.undo() console.log(calculator.value) // 10 class AddThenMultiplyCommand { constructor(valueToAdd,valueToMultiply) { this.addCommand = new AddCommand(valueToAdd) this.multiplyCommand = new MultiplyCommand(valueToMultiply) } execute(currentValue) { const newValue = this.addCommand.excute(currentValue) retrun this.multiplyCommand.execute(newValue) } undo(currentValue) { const newValue = this.multiplyCommand.undo(currentValue) retrun this.addCommand.undo(newValue) } } calculator.executeCommand(new AddThenMultiplyCommand(10,2)) console.log(calculator.value) // 20 calculator.undo() console.log(calculator.value) // 0