본문 바로가기
Dev/디자인패턴

[타입스크립트로 살펴보는 디자인패턴 6] 커맨드 패턴

by 싯벨트 2024. 12. 28.
728x90

🙋‍♂️ 디자인패턴 구현코드 깃헙

커맨드 패턴(Command Pattern)

커맨드 패턴을 사용하면 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다. 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다.

커맨드 패턴을 이해하기 위해 카페에서 커피를 구매하는 경우를 살펴보자. 고객은 카페에 가서 종업원에게 주문을 한다. 종업원은 주문내역을 적은 주문서바리스타에게 넘긴다. 바리스타는 주문내역에 맞게 커피를 만들어서 고객에게 전달한다.

여기서 주체는 4개가 있다. 고객, 주문서, 종업원, 바리스타이다. 커맨드 패턴에서는 각 주체들을 순서대로 클라이언트, 커맨드, 인보커, 리시버라고 칭한다. 객체들의 의사소통은 커피 주문의 예시와 동일하다.

  1. 클라이언트가 요청을 한다.
  2. 인보커는 커맨드를 입력한다.
  3. 인보커가 커맨드를 실행한다.
  4. 해당 커맨드를 수행하는 리시버가 응답을 리턴한다.

커맨드 패턴 클래스 다이어그램

 

커맨드 패턴의 핵심은 요청하는 개체와 요청을 수행하는 객체를 분리할 수 있다는 것이다. 커맨드 객체는 일련의 행동을 특정 리시버와 연결하여 요청을 캡슐화한다. execute()메서드만 외부에 공개하기 때문에 외부에서는 어떤 객체가 리시버 역할을 하는지 모른다. 인보커는 무언가 요청할 때는 커멘드 객체의 execute() 메서드를 호출하면 된다.

on/off 리모컨 

코드

command.ts

커멘드 인터페이스는 실행과 실행 취소에 대한 메서드를 갖는다.

export interface Command {
    execute(): void;
    undo(): void;
}

light.on.command.ts

커맨드 객체는 특정 리시버의 행동을 연결한다. 실행취소에는 커맨드의 반대 행동을 연결한다.

import { Command } from "../command";
import { Light } from "./light";

export class LightOnCommand implements Command {
    constructor(private light: Light) {}

    execute(): void {
        this.light.on();
    }

    undo(): void {
        this.light.off();
    }
}

remote.control.ts

인보커인 리모컨은 버튼을 눌렀을 때, 슬롯별로 지정된 커맨드 객체의 execute()메서드를 실행한다.

import { Command } from "./command/command";
import { NoCommand } from "./command/no.command";

export class RemoteControl {
    private onCommands: Command[];
    private offCommands: Command[];
    private undoCommand: Command;

    constructor() {
        this.onCommands = Array.from({ length: 7 }, () => new NoCommand());
        this.offCommands = Array.from({ length: 7 }, () => new NoCommand());
        this.undoCommand = new NoCommand();
    }

    setCommand(slot: number, onCommand: Command, offCommand: Command): void {
        this.onCommands[slot] = onCommand;
        this.offCommands[slot] = offCommand;
    }

    onButtonWasPushed(slot: number): void {
        this.onCommands[slot].execute();
        this.undoCommand = this.onCommands[slot];
    }

    offButtonWasPushed(slot: number): void {
        this.offCommands[slot].execute();
        this.undoCommand = this.offCommands[slot];
    }

    undoButtonWasPushed(): void {
        this.undoCommand.undo();
    }

    showButtons(): string {
        let remote = "---- 리모컨 ----\\n";
        for (let i = 0; i < this.onCommands.length; i++) {
            remote += `[slot ${i}] ${this.onCommands[i].constructor.name} ${this.offCommands[i].constructor.name}\\n`;
        }
        return remote;
    }
}

remote.control.test.ts

클라이언트는인 테스트 파일에서는 각 슬롯에 특정 커맨드를 연결하고 인보커를 통해 커맨드를 실행한다.

import { Garage } from "./command/garage/garage";
import { GarageDoorCloseCommand } from "./command/garage/garage.door.close.command";
import { GarageDoorOpenCommand } from "./command/garage/garage.door.open.command";
import { Light } from "./command/light/light";
import { LightOffCommand } from "./command/light/light.off.command";
import { LightOnCommand } from "./command/light/light.on.command";
import { RemoteControl } from "./remote.control";

class RemoteControlTest {
    remoteControl = new RemoteControl();
    livingRoomLight = new Light("거실");
    kitchenLight = new Light("주방");
    garageLight = new Light("차고");
    garage = new Garage();

    livingRoomLightOn = new LightOnCommand(this.livingRoomLight);
    livingRoomLightOff = new LightOffCommand(this.livingRoomLight);
    kitchenLightOn = new LightOnCommand(this.kitchenLight);
    kitchenLightOff = new LightOffCommand(this.kitchenLight);
    garageLightOn = new LightOnCommand(this.garageLight);
    garageLightOff = new LightOffCommand(this.garageLight);
    garageDoorOpen = new GarageDoorOpenCommand(this.garage);
    garageDoorClose = new GarageDoorCloseCommand(this.garage);

    test(): void {
        this.remoteControl.setCommand(0, this.livingRoomLightOn, this.livingRoomLightOff);
        this.remoteControl.setCommand(1, this.kitchenLightOn, this.kitchenLightOff);
        this.remoteControl.setCommand(2, this.garageLightOn, this.garageLightOff);
        this.remoteControl.setCommand(3, this.garageDoorOpen, this.garageDoorClose);

        this.remoteControl.onButtonWasPushed(0);
        this.remoteControl.offButtonWasPushed(0);
        console.log(this.remoteControl.showButtons());
        this.remoteControl.undoButtonWasPushed();
        console.log("-- next --\\n");
        this.remoteControl.offButtonWasPushed(0);
        this.remoteControl.onButtonWasPushed(0);
        console.log(this.remoteControl.showButtons());
        this.remoteControl.undoButtonWasPushed();
    }
}

new RemoteControlTest().test();

리모콘 테스트 출력값