커맨드 패턴(Command Pattern)

wannabeking·2022년 10월 10일
0

디자인 패턴

목록 보기
7/14
post-thumbnail

해당 예시는 헤드 퍼스트 디자인 패턴을 참고했습니다.

만능 리모콘

위와 같은 생김새의 만능 리모콘을 개발해야 한다.

좌측의 0, 1, 2, 3 인덱스에 등록하여 어떠한 기기의 특정 기능을 수행하거나(ON) 꺼버릴 수(OFF) 있다.

예를 들어 0번째 인덱스의 ON에 불을 켜는 기능, OFF에 불을 끄는 기능 등록

각 인덱스에는 새로운 기능을 수행하도록 기능을 교체할 수 있어야 한다.

UNDO 버튼은 마지막으로 실행한 명령을 되돌리는 기능을 수행한다.

마지막으로 불을 키는 동작을 수행했다면, UNDO 버튼은 불을 끔


자, 이제 커맨드 패턴을 사용하여 만능 리모콘을 구현해보자!



커맨드 패턴

커맨드 패턴은 다음과 같이 구성된다.

  • Command : 실행될 기능에 대한 인터페이스, execute()의 구현을 강제하며 위의 예제에서는 되돌리는 기능도 존재하기 때문에 undo()도 존재
  • Concrete Command : execute()를 실제로 구현하는 클래스
  • Invoker : 기능을 전달 받고 호출해주는 클래스, 예제에서 리모콘에 해당
  • Receiver : 실제 작업을 수행하는 클래스

만능 리모콘 예제를 구현하면서 커맨드 패턴의 구성 요소들을 위에서부터 차례대로 살펴보자.


public interface Command {

    void execute();

    void undo();
}

기능이라는 단위를 추상화 시키면, Invoker에서 어떠한 기능인지 몰라도 단순히 execute()undo()를 호출하여 느슨한 결합으로 이루어질 수 있다.


public class LightOnCommand implements Command {

    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}
public class LightOffCommand implements Command {

    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        light.on();
    }
}

Concrete Command에서 Command에서 정의된 execute()undo()를 구현했다.

LightOnCommand의 경우 실행되면 불을 키므로 취소되면 불을 끄면 될 것이다.

어떠한 기능(작업)의 단위에선 실제로 작업을 수행하는 클래스(Receiver)가 필요한데, 여기서 Light에 해당된다.


public class RemoteControl {

    private static final int COMMAND_NUM = 3;
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;

    public RemoteControl() {
        onCommands = new Command[COMMAND_NUM];
        offCommands = new Command[COMMAND_NUM];
        for (int i = 0; i < COMMAND_NUM; i++) {
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }
        undoCommand = new NoCommand();
    }

    public void setCommand(int idx, Command onCommand, Command offCommand) {
        onCommands[idx] = onCommand;
        offCommands[idx] = offCommand;
    }

    public void onBtnPressed(int idx) {
        Command onCommand = onCommands[idx];
        onCommand.execute();
        undoCommand = onCommand;
    }

    public void offBtnPressed(int idx) {
        Command offCommand = offCommands[idx];
        offCommand.execute();
        undoCommand = offCommand;
    }

    public void undoBtnPressed() {
        undoCommand.undo();
    }
}

위의 예제의 리모콘(Invoker)를 구현한 클래스이다.

각 인덱스에 맞게 ON 커맨드, OFF 커맨드를 등록할 수 있고 각 버튼에 맞게 Commandexecute()를 호출하여 기능을 수행한다.

UNDO 버튼을 클릭할 경우를 대비하여 마지막 명령을 undoCommand에 저장한다.


public class Light {

    boolean isTurnedOn;

    public Light() {
        isTurnedOn = false;
    }

    public void on() {
        isTurnedOn = true;
        System.out.println("불을 켭니다.");
    }

    public void off() {
        isTurnedOn = false;
        System.out.println("불을 끕니다.");
    }

    public boolean isTurnedOn() {
        return isTurnedOn;
    }
}

실제 작업을 처리하는 Receiver다.

리모컨에 ON, OFF 버튼밖에 없다고 해당 작업만 수행할 수 있는게 아니라, 만약 불을 반만 켤 수 있는 기능이 필요하다면 LightHalfOnCommand 라는 기능을 추가하여 Invoker에 등록 후 사용하면 된다.

만약 반만 켜는 기능을 추가하면, 현재는 isTurnedOn으로 true or false이기 때문에 Enum으로 변경하여 관리하거나 int를 사용하면 될 것이다.


이제 커맨드 패턴의 구성에 대해 파악했으니, psvm을 살펴보자.

필자는 선풍기도 추가하여 OFF, LOW, HIGH로 바람의 세기를 조절할 수 있게 리모콘에 등록하였다.


public class Main {

    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();

        Light light = new Light();
        Fan fan = new Fan();

        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
        Command fanLowCommand = new FanLowCommand(fan);
        Command fanHighCommand = new FanHighCommand(fan);
        Command fanOffCommand = new FanOffCommand(fan);

        remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
        remoteControl.setCommand(1, fanLowCommand, fanOffCommand);
        remoteControl.setCommand(2, fanHighCommand, fanOffCommand);

        remoteControl.onBtnPressed(0);
        remoteControl.undoBtnPressed();
        remoteControl.onBtnPressed(1);
        remoteControl.onBtnPressed(2);
        remoteControl.undoBtnPressed();
        remoteControl.offBtnPressed(1);
    }
}

위에서 부터 설명하면,

remoteControl이라는 Invoker를 생성했다.

실제 작업을 처리하는 Receiver에 해당되는 lightfan을 생성했다.

Concrete Command들을 생성했다.
Invoker가 추상적으로 사용하여 결국 Receiver가 작업을 처리하므로, Concrete Command는 Invoker와 Receiver 사이를 연결하는 매개체라고 생각할 수 있다.

Invoker에 커맨드를 등록한다.
필자는 다음과 같이 커맨드를 등록했다.

0 : 불 켜기(ON), 불 끄기(OFF)
1 : 선풍기 약한 바람(ON), 선풍기 끄기(OFF)
2 : 선풍기 강한 바람(ON), 선풍기 끄기(OFF)

이제 출력을 살펴보자.


지금까지 살펴본 것처럼 커맨드 패턴은 요청(커맨드)을 캡슐화하여 사용한다.
따라서 요청하는 객체와 요청을 수행하는 객체를 분리할 수 있다.

필요에 따라 요청을 큐에 저장하거나 로그로 기록하거나 구현했던 것 처럼 취소하는 기능을 사용할 수 있으므로 참고하자.


모든 소스코드는 여기에서 확인할 수 있다.



profile
내일은 개발왕 😎

0개의 댓글