"JavaScript Design Pattern" 포스팅은 ChatGPT에게 "Essential design patterns for JavaScript developers to learn"로 질문한 뒤 나온 내용에 대해 정리하는 포스팅입니다.
1. 커맨드 패턴(Command Pattern)
커맨드 패턴은 요청이나 동작을 객체로 캡슐화하여 전달하고 조작하기 쉽게 만드는 행동 디자인 패턴이다.
JavaScript에서 커맨드 패턴은 동작 요청자와 동작을 수행하는 객체를 분리하는 데 사용되어 코드의 유연성과 재사용성을 향상한다.
JavaScript에서 커맨드 패턴은 다음 컴포넌트를 사용하여 구현할 수 있다.
- Command: 모든 명령에 대한 계약을 정의하는 인터페이스이다. 일반적으로 단일 실행 메서드를 포함한다.
class Command {
execute() {}
}
- ConcreteCommand: 요청과 해당 매개 변수를 캡슐화하는 Command 인터페이스의 구현이다.
class ConcreteCommand extends Command {
constructor(receiver, args) {
super();
this.receiver = receiver;
this.args = args;
}
execute() {
this.receiver.action(this.args);
}
}
- Receiver: 명령과 관련된 실제 작업을 수행하는 객체이다.
class Receiver {
action(args) {
console.log(`Performing action with ${args}`);
}
}
- Invoker: 명령을 보관하고 필요할 때 명령을 호출하는 객체이다.
class Invoker {
constructor() {
this.command = null;
}
setCommand(command) {
this.command = command;
}
executeCommand() {
this.command.execute();
}
}
커맨드 패턴을 사용하면 Invoker의 인스턴스를 생성하고 그 위에 ConcreteCommand를 설정할 수 있다.
Invoker에서 executeCommand() 메서드를 실행하면 ConcreteCommand의 execute() 메서드가 호출되며, 이 메서드는 다시 Reciver의 action() 메서드를 호출한다.
const receiver = new Receiver();
const command = new ConcreteCommand(receiver, 'some arguments');
const invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand(); // Logs "Performing action with some arguments"
이를 통해 요청을 보내는 객체와 요청과 관련된 작업을 수행하는 객체를 분리할 수 있으며, 보낸 사람과 수신자 객체를 분리할 수 있다.
또한, 커맨드 패턴은 요청과 해당 매개 변수를 별도의 객체에 캡슐화하기 때문에 기존 코드를 수정하지 않고도 새 명령 및 수신기를 쉽게 추가할 수 있다.
다음은 JavaScript에서 커맨드 패턴을 구현하는 예이다:
// Receiver 객체
class Light {
turnOn() {
console.log('Light is on');
}
turnOff() {
console.log('Light is off');
}
}
// Command 객체
class LightOnCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOn();
}
}
// Command 객체
class LightOffCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOff();
}
}
// Invoker 객체
class RemoteControl {
constructor() {
this.commands = [];
}
addCommand(command) {
this.commands.push(command);
}
executeCommands() {
this.commands.forEach((command) => command.execute());
this.commands = [];
}
}
// Usage
const light = new Light();
const lightOnCommand = new LightOnCommand(light);
const lightOffCommand = new LightOffCommand(light);
const remoteControl = new RemoteControl();
remoteControl.addCommand(lightOnCommand);
remoteControl.addCommand(lightOffCommand);
remoteControl.executeCommands();
이 예제에는 각각 조명을 켜고 끄는 메서드인 turnOn()과 turnOff()가 있는 Light 객체가 있다.
또한, Light 객체를 생성자 인수로 사용하고 Light 객체에서 해당 메서드를 호출하는 execute() 메서드를 구현하는 두 개의 Command 객체 LightOnCommand와 LightOffCommand가 있다.
그리고 Command 객체 목록을 유지하고 execute() 메서드를 호출하여 실행할 수 있는 Invoker 객체 RemoteControl이 있다.
RemoteControl의 commands 목록에 LightOnCommand와 LightOffCommand 객체를 추가한 다음, remoteControl.executeCommands()를 호출하여 한 번에 모두 실행할 수 있다.
이러한 방식으로 커맨드 패턴을 사용하면 작업을 요청하는 코드(Invoker)와 작업을 수행하는 코드(Receiver 및 Command 객체)를 분리할 수 있다.
따라서, Invoker 또는 Receiver 코드를 수정하지 않고도 새 명령을 쉽게 추가할 수 있으며, 다른 컨텍스트에서 명령을 재사용하는 것도 쉽다.
2. Command Pattern 연습 코드
ChatGPT에게 "Give me a challenge to practice the Command Pattern in JavaScript."라고 질문한 뒤 나온 문제와 그에 대한 답이다.
ChatGPT가 계속 스켈레톤 코드가 아닌 완성된 코드를 제시하여 커맨드 패턴의 연습 코드는 ChatGPT가 작성한 코드만 올리기로 한다.
// Receiver object
class TV {
constructor() {
this.isOn = false;
this.channel = 1;
this.volume = 50;
}
turnOn() {
this.isOn = true;
console.log('TV가 켜졌습니다.');
}
turnOff() {
this.isOn = false;
console.log('TV가 꺼졌습니다.');
}
changeChannelUp() {
if (this.isOn) {
this.channel++;
console.log(`채널이 ${this.channel}(으)로 변경되었습니다.`);
}
}
changeChannelDown() {
if (this.isOn) {
this.channel--;
console.log(`채널이 ${this.channel}(으)로 변경되었습니다.`);
}
}
volumeUp() {
if (this.isOn) {
this.volume++;
console.log(`볼륨이 ${this.volume}(으)로 조정되었습니다.`);
}
}
volumeDown() {
if (this.isOn) {
this.volume--;
console.log(`볼륨이 ${this.volume}(으)로 조정되었습니다.`);
}
}
}
// Command object
class TVOnCommand {
constructor(tv) {
this.tv = tv;
}
execute() {
this.tv.turnOn();
}
undo() {
this.tv.turnOff();
}
}
// Command object
class TVOffCommand {
constructor(tv) {
this.tv = tv;
}
execute() {
this.tv.turnOff();
}
undo() {
this.tv.turnOn();
}
}
// Command object
class TVChangeChannelUpCommand {
constructor(tv) {
this.tv = tv;
this.previousChannel = null;
}
execute() {
this.previousChannel = this.tv.channel;
this.tv.changeChannelUp();
}
undo() {
this.tv.channel = this.previousChannel;
console.log(`채널이 ${this.previousChannel}(으)로 변경되었습니다.`);
}
}
// Command object
class TVChangeChannelDownCommand {
constructor(tv) {
this.tv = tv;
this.previousChannel = null;
}
execute() {
this.previousChannel = this.tv.channel;
this.tv.changeChannelDown();
}
undo() {
this.tv.channel = this.previousChannel;
console.log(`채널이 ${this.previousChannel}(으)로 변경되었습니다.`);
}
}
// Command object
class TVVolumeUpCommand {
constructor(tv) {
this.tv = tv;
this.previousVolume = null;
}
execute() {
this.previousVolume = this.tv.volume;
this.tv.volumeUp();
}
undo() {
this.tv.volume = this.previousVolume;
console.log(`볼륨이 ${this.previousVolume}(으)로 조정되었습니다.`);
}
}
// Command object
class TVVolumeDownCommand {
constructor(tv) {
this.tv = tv;
this.previousVolume = null;
}
execute() {
this.previousVolume = this.tv.volume;
this.tv.volumeDown();
}
undo() {
this.tv.volume = this.previousVolume;
console.log(`볼륨이 ${this.previousVolume}(으)로 조정되었습니다.`);
}
}
// Invoker object
class RemoteControl {
constructor() {
this.commands = [];
this.undoStack = [];
}
setCommand(command) {
this.commands.push(command);
}
executeCommands() {
this.commands.forEach((command) => {
command.execute();
this.undoStack.push(command);
});
this.commands = [];
}
undoLastCommand() {
const commandToUndo = this.undoStack.pop();
if (commandToUndo) {
commandToUndo.undo();
}
}
}
// Usage
const tv = new TV();
const remote = new RemoteControl();
// Create command objects
const turnOnCommand = new TVOnCommand(tv);
const turnOffCommand = new TVOffCommand(tv);
const changeChannelUpCommand = new TVChangeChannelUpCommand(tv);
const changeChannelDownCommand = new TVChangeChannelDownCommand(tv);
const volumeUpCommand = new TVVolumeUpCommand(tv);
const volumeDownCommand = new TVVolumeDownCommand(tv);
// Set commands to the remote control
remote.setCommand(turnOnCommand);
remote.setCommand(changeChannelUpCommand);
remote.setCommand(volumeUpCommand);
remote.setCommand(volumeUpCommand);
remote.setCommand(volumeUpCommand);
// Execute commands and undo the last command
remote.executeCommands();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand();
console.log('현재 TV 상태:', tv);
remote.undoLastCommand(); // 실행되지 않음
console.log('현재 TV 상태:', tv);
'Web > JS' 카테고리의 다른 글
[JS] eslint를 사용할 때 hasOwnProperty 사용 시 오류 (0) | 2023.05.09 |
---|---|
[JS] 디자인 패턴(Design Pattern)_전략 패턴(Strategy Pattern) (0) | 2023.03.03 |
[JS] 디자인 패턴(Design Pattern)_퍼사드 패턴(Facade Pattern) (0) | 2023.03.01 |
[JS] 디자인 패턴(Design Pattern)_어댑터 패턴(Adapter Pattern) (0) | 2023.02.27 |
[JS] 디자인 패턴(Design Pattern)_데코레이터 패턴(Decorator Pattern) (0) | 2023.02.26 |