소프트웨어 관련/Design Pattern

[Design Pattern] Command Pattern

JJangGu 2022. 5. 13. 01:28

이번에는 커맨드 패턴입니다. 

 

Command Pattern

커맨드 패턴을 이용하면 요구 사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러가지 다른 요구 사항을 집어넣을 수도 있다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업 취소 기능도 지원 가능하다.

 

먼저 다이어그램을 보면서 어떤 모양일지 얘기해보겠습니다. 이제 디자인 패턴을 1/3 정도 보다보니, 인터페이스를 잘 활용하는 것이 중요하다고 생각이 듭니다. 캡슐화하거나 추상화하거나 감싸거나 하는 것들에 인터페이스가 들어가거나 혹은 구성을 이용한다거나 하는 것 같습니다.

 

앞으로는 바로 다이어그램을 보면서 어떤 모양일지 추리해보는 것도 좋은 공부방법이 아닐까 하는 생각이 드네요 🤔

 

 

다이어그램을 보겠습니다. Client 는 ConcreteCommand 를 생성하고 Receiver 를 설정합니다. ConcreteCommand 는 특정 행동과 Receiver 사이를 연결하고, Invoker 에서 execute() 호출을 통해 요청을 하면, ConcreteCommand 객체에서 Receiver 에 있는 메소드를 호출함으로써 그 작업을 처리합니다.

 

Command 는 모든 커맨드 객체에서 구현해야 하는 인터페이스입니다. 모든 명령은 execute() 메소드 호출을 통해 수행되며, 이 메소드에서는 Receiver 에 특정 작업을 처리하라는 지시를 전달합니다. Invoker 는 execute() 메소드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요청을 하게 됩니다. 

 

이제 책에 있는 리모컨 예제로 코드의 모양을 보겠습니다. 

 

먼저, Command 인터페이스입니다. 

 

public interface Command {
    void execute();
}

 

Command 를 구현한 ConcreteCommand 클래스를 보겠습니다. 

 

public class LightOnCommand implements Command {
    Light light;

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

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

 

다음으로는 Invoker 클래스인 RemoteController 입니다. 

 

public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

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

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }
}

 

리모컨 코드에서 setCommand 를 보면 슬롯 번화와 슬롯에 저장할 ON 및 OFF 명령을 인자로 전달 받습니다. 각 커맨드 객체를 나중에 사용할 수 있도록 배열에 저장하고 있습니다. 그리고 onButtonWasPushed() 가 호출되면 slot 에 맞게 execute가 동작하게 만듭니다. 

 

전체 소스는 여기서 보실 수 있습니다. :)

https://github.com/KyungSik9870/DesignPattern/tree/master/src/study/pattern/command

 

GitHub - KyungSik9870/DesignPattern: Design Patterns - Head First 의 Design Pattern 의 예제를 기준으로 디자인 패턴

Design Patterns - Head First 의 Design Pattern 의 예제를 기준으로 디자인 패턴 학습 - GitHub - KyungSik9870/DesignPattern: Design Patterns - Head First 의 Design Pattern 의 예제를 기준으로 디자인 패턴 학습

github.com

 

커맨드 패턴의 활용으로 '요청을 큐에 저장하기' 와 '요청을 로그에 기록하기' 이렇게 두개의 예시를 들고 있는데요, 이것도 어떻게 동작할지 예상해보겠습니다. 

 

커맨드 패턴을 이용하면 리시버와 어떤 일련의 행동을 묶어서 일급객체 형태로 전달하는 것이 가능합니다. 그러면 그 일련의 행동을 호출할 수 있죠. 작업 큐에 대해서 생각해보면, 한 쪽 끝은 커맨드를 추가할 수 있게 되어 있고, 다른 한 쪽에는 커맨드를 처리하기 위한 스레드가 있다고 가정할 수 있습니다. 그러면 각 스레드에서는 우선 execute() 를 호출하고, 호출이 완료되고 나면 커맨드 객체를 보내고 새로운 커맨드 객체를 가져옵니다. 

 

그러면 스레드의 입장에서는 커맨드를 가져와서 execute() 만 호출하면 됩니다. 이렇게 함으로써 커맨드는 스레드의 작업에 대해서 신경쓰지 않아도 되고, 스레드도 execute() 호출을 통해서 여러가지 작업을 처리 할 수 있죠. 

 

그렇다면 로그를 기록할 때는 어떻게 할까요? 

 

Command 인터페이스에 execute(), store(), load() 이렇게 있다고 가정해봅시다. 그러면 execute() 로 각 커맨드가 실행될때 store() 를 수행하여 디스크에 작업을 기록하게 만들 수 있습니다. 그 후에 작업이 실패했을때 load() 를 통해서 저장된 작업을 가져와서 복구하는 것이죠. 어플리케이션의 크기 등을 고려해서 저장되는 데이터를 변경할 수도 있을 겁니다. 

 

커맨드의 요점은 결국 요청내역을 객체로 캡슐화 한다는 것에 있습니다. 그러면서 요청 내역을 매개변수화 하는 것이죠. 

 

커맨드 패턴에 대한 학습은 여기까지 하겠습니다. 👍