커맨드 패턴

Bo Ram·2025년 3월 19일

1. 커맨드 패턴이란?

커맨드 패턴(Command Pattern)은 요청을 객체로 캡슐화하여 요청의 실행, 취소(Undo), 저장을 가능하게 하는 디자인 패턴이다. 이 패턴을 활용하면 실행 요청을 별도의 객체로 분리할 수 있어 코드를 유연하고 확장 가능하게 만들 수 있다.

2. 커맨드 패턴이 필요한 이유

일반적인 방법으로 요청을 수행하면 특정 객체에 직접 메시지를 보내게 된다. 하지만 이렇게 하면 클라이언트와 수신자(Receiver) 간의 결합도가 높아져 유지보수가 어려워진다.

커맨드 패턴을 사용하면 클라이언트와 요청을 수행하는 객체(수신자) 사이에 커맨드 객체를 두어 결합도를 낮추고, 다양한 기능(예: 실행 취소, 큐에 저장 등)을 추가할 수 있다.

3. 커맨드 패턴의 구조

클래스 다이어그램

Client -> Invoker -> Command -> Receiver
  • Client (클라이언트): 커맨드 객체를 생성하고 요청자(Invoker)에게 전달
  • Invoker (요청자): 커맨드를 실행하는 역할 (예: 리모컨)
  • Command (명령 인터페이스): 실행할 동작을 정의하는 인터페이스
  • ConcreteCommand (구체적인 명령 클래스): 실제 실행될 커맨드를 구현
  • Receiver (수신자): 실제 요청을 수행하는 객체 (예: 전등, 오디오 등 가전제품)

4. 커맨드 패턴 구현 (리모컨 예제)

// 1. Command 인터페이스 정의
public interface Command {
    void execute();
    void undo();
}

// 2. Receiver (실제 작업을 수행하는 클래스)
public class Light {
    public void on() {
        System.out.println("Light is ON");
    }
    
    public void off() {
        System.out.println("Light is OFF");
    }
}

// 3. ConcreteCommand (명령 객체)
public class LightOnCommand implements Command {
    private Light light;

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

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

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

// 4. Invoker (요청을 저장하고 실행하는 객체)
public class RemoteControl {
    private Command slot;

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

    public void pressButton() {
        slot.execute();
    }

    public void pressUndo() {
        slot.undo();
    }
}

// 5. Client (사용 예)
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        Light livingRoomLight = new Light();
        Command lightOn = new LightOnCommand(livingRoomLight);

        remote.setCommand(lightOn);
        remote.pressButton();  // "Light is ON" 출력
        remote.pressUndo();    // "Light is OFF" 출력
    }
}

5. 실행 취소(Undo) 기능 추가

커맨드 패턴의 가장 큰 장점 중 하나는 실행 취소 기능을 쉽게 추가할 수 있다는 것이다.

// 실행 취소를 지원하는 리모컨
public class RemoteControlWithUndo {
    private Command lastCommand;

    public void setCommand(Command command) {
        this.lastCommand = command;
    }

    public void pressButton() {
        lastCommand.execute();
    }

    public void pressUndo() {
        lastCommand.undo();
    }
}

6. 매크로 커맨드 (여러 개의 커맨드를 하나로 묶기)

여러 개의 명령을 한꺼번에 실행하는 매크로 기능을 구현할 수도 있다.

public class MacroCommand implements Command {
    private Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    @Override
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }

    @Override
    public void undo() {
        for (Command command : commands) {
            command.undo();
        }
    }
}

7. 커맨드 패턴의 장점

  • 결합도 감소: 클라이언트와 수신자가 직접 연결되지 않아 유연성이 증가
  • 실행 취소(Undo) 기능 추가 가능: undo() 메서드를 활용하여 실행 취소 구현
  • 작업 요청을 큐에 저장 가능: 요청을 저장하고 나중에 실행 가능
  • 작업 요청을 로그로 기록 가능: 시스템에서 수행된 작업을 기록할 수 있음
  • OCP(Open-Closed Principle) 준수: 새로운 명령을 추가해도 기존 코드를 수정할 필요 없음

8. 커맨드 패턴의 단점

  • 클래스 수 증가: 각 명령마다 별도의 클래스가 필요하여 코드가 많아질 수 있음
  • 간단한 작업에는 오버헤드가 있을 수 있음

9. 커맨드 패턴 활용 사례

  • GUI 버튼 클릭 이벤트 처리
  • 스마트 홈 시스템 (음성 명령, IoT 기기 제어 등)
  • 매크로 기능 (여러 명령을 하나로 묶어 실행)
  • 게임에서 캐릭터의 행동을 기록하고 되돌리기 기능 구현
  • 멀티스레드 환경에서 작업 요청을 큐에 저장하고 순차 실행
profile
사부작ㅤ사부작

0개의 댓글