flagtags / OOP-study

0 stars 0 forks source link

6장 커맨드 패턴 #19

Open j03y14 opened 4 months ago

j03y14 commented 4 months ago

리모컨 구현 예시


커멘드 패턴을 써보자

커멘드 패턴

객체마을 식당 예시

요구하는 객체와 처리하는 객체를 분리한 것.

첫번째 커맨드 객체 만들기

interface Command {
    execute(): void;
}

class Light {
    on() {
        console.log('Light is on');
    }
    off() {
        console.log('Light is off');
    }
}

class LightOnCommand implements Command {
    light: Light;

    constructor(light: Light) {
        this.light = light;
    }

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

class SimpleRemoteControl {
    slot: Command;

    setCommand(command: Command) {
        this.slot = command;
    }

    buttonWasPressed() {
        this.slot.execute();
    }
}

커맨드 패턴의 정의

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

리모컨 구현

interface Command {
    execute(): void;
}

class Light {
    on() {
        console.log('Light is on');
    }
    off() {
        console.log('Light is off');
    }
}

class Stereo {
    on() {
        console.log('Stereo is on');
    }

    off() {
        console.log('Stereo is off');
    }

    setCD() {
        console.log('Stereo is set for CD input');
    }

    setVolume(volume: number) {
        console.log(`Stereo volume set to ${volume}`);
    }
}

class NoCommand implements Command {
    execute() {}
}

class LightOnCommand implements Command {
    light: Light;

    constructor(light: Light) {
        this.light = light;
    }

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

class LightOffCommand implements Command {
    light: Light;

    constructor(light: Light) {
        this.light = light;
    }

    execute() {
        this.light.off();
    }
}

class StereoOnWithCDCommand implements Command {
    stereo: Stereo;

    constructor(stereo: Stereo) {
        this.stereo = stereo;
    }

    execute() {
        this.stereo.on();
        this.stereo.setCD();
        this.stereo.setVolume(11);
    }
}

class RemoteControl {
    onCommands: Command[];
    offCommands: Command[];
    slotNumer: number

    constructor() {
        // null 체크를 하고싶지 않아서 NoCommand를 사용.
        const noCommand = new NoCommand();
        this.slotNumer = 7;
        this.onCommands = Array.from({ length: this.slotNumer }, () => noCommand);
        this.offCommands = Array.from({ length: this.slotNumer }, () => noCommand);
    }

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

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

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

    toString() {
        return Array.from({ length: this.slotNumer }, (_, index: number) => {
            return `slot[${index}] ${this.onCommands[index].constructor.name} ${this.offCommands[index].constructor.name}`
        }).join('\n');

    }
}

NoCommand null 객체를 사용해서 null 체크를 하지 않아도 되도록 수정.

커맨드 객체 대신 람다 사용

Command 객체에 추상메서드가 하나일 때만 사용. 예를 들어, Command에 execute 말고 다른 메서드도 생긴다면 사용 불가.

kkirico commented 4 months ago

리모콘 예시

공통적인 인터페이스가 없이, 리모콘의 여러개의 버튼 쌍으로 다양한 종류의 작업을 수행해야한다.

새로운 종류의 작업이 계속 추가될 수 있으며, 리모콘 코드를 변경하지 않는 방법이 있을까?

커맨드 패턴 살펴보기

커맨드 패턴을 사용하면 작업을 요청하는 쪽과 그 작업을 처리하는 쪽을 분리할 수 있다.

디자인에 커맨드 객체를 추가한다.

커맨드 객체는 특정 객체와 그에 관한 특정 작업 요청을 캡슐화해준다.

음식 주문 과정 예시

손님이 주문을 한다 → 주문서에 해당 내용을 적는다 → 종업원이 요리사에게 전달한다 → 요리가 나온다.

‘주문서’에 주문의 내용이 캡슐화됩니다. 종업원은 주문의 내용을 몰라도 무관하다. 주문서 객체에 orderUp() 메소드가 있어서 그걸 호출하기만 하면 된다. 요리사는 주문의 처리 방법을 알고있다. 주문서를 전달받아 주문된 메뉴를 제작한다.

이 상황은 어떤 것을 요구하는 객체와 그 요구를 받아들이고 처리하는 객체를 분리한다는데에 의미가 있습니다.

커맨드 패턴 적용

코드 생략

커맨드 패턴 정의

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화 한다.

행동과 리시버를 한 객체에 넣고, execute() 메소드 만 외부에 노출한다.

동작이 필요없는경우 널 커맨드 객체를 할당시킬 수 있다

한번에 여러개의 커맨드가 실행되는 매크로 커맨드 패턴을 사용할 수 있다.(여러개의 커맨드 배열을 할당하고 하나씩 실행한다.)