PostCover

디자인 패턴을 쉽게 풀어보자 - 커맨드 패턴편

GoF의 23가지 디자인 패턴 중 하나인 '커맨드 패턴(Command Pattern)'을 쉽게 풀어보기

커맨드(Command) 패턴

커맨드 패턴은 요청을 객체로 캡슐화하여 요청자와 수행자 간의 결합을 느슨하게 만드는 행동 디자인 패턴입니다. 이 패턴을 사용하면 요청을 큐에 저장하거나 로그로 기록할 수 있고, 실행 취소(Undo) 기능도 쉽게 구현할 수 있습니다.

문제점: 요청과 수행 객체 간의 강한 결합

일반적인 코드에서는 **요청자(Invoker)**가 수행 객체(Receiver)에 대해 직접 호출을 수행합니다. 이 방식은 결합도가 높아 유지보수가 어려워지고, 동적인 요청 처리가 복잡해질 수 있습니다.

class Light {

    public void turnOn() {
        System.out.println("불 좀 켜줄래?");
    }

    public void turnOff() {
        System.out.println("불 좀 꺼줄래?");
    }
}

public class Main {

    public static void main(String[] args) {
        Light light = new Light();
        light.turnOn();
        light.turnOff();
    }
}

문제점

  • 결합도 증가: 클라이언트가 직접 Light 객체의 메서드를 호출합니다.
  • 유연성 부족: 나중에 다른 명령을 추가하거나, 요청을 큐에 저장하기 어려워집니다.
  • 실행 취소 기능을 구현하기가 복잡합니다.

해결 방법: 커맨드 패턴 적용

커맨드 패턴에서는 요청을 **명령 객체(Command)**로 캡슐화합니다. 요청자는 명령 객체를 통해 수행 객체를 호출하므로, 요청자와 수행 객체 간의 결합도를 줄이고 더 유연하게 요청을 처리할 수 있습니다.

커맨드 인터페이스 정의

interface Command {
    void execute();
    void undo();  // 실행 취소 기능을 위한 메서드
}

Receiver(수행 객체) 클래스 구현

class Light {
    public void turnOn() {
        System.out.println("불 좀 켜줄래?");
    }

    public void turnOff() {
        System.out.println("불 좀 꺼줄래?");
    }
}

구체적인 커맨드 클래스 구현

class LightOnCommand implements Command {

    private Light light;

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

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

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

class LightOffCommand implements Command {

    private Light light;

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

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

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

Invoker(요청자) 클래스 구현

class RemoteControl {

    private Command command;

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

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

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

클라이언트 코드

public class Main {
    public static void main(String[] args) {
        Light light = new Light();
        Command lightOn = new LightOnCommand(light);
        Command lightOff = new LightOffCommand(light);

        RemoteControl remote = new RemoteControl();

        // 불 켜기 명령 실행
        remote.setCommand(lightOn);
        remote.pressButton();  // 불 좀 켜줄래?

        // 실행 취소 (불 끄기)
        remote.pressUndo();  // 불 좀 꺼줄래?

        // 불 끄기 명령 실행
        remote.setCommand(lightOff);
        remote.pressButton();  // 불 좀 꺼줄래?

        // 실행 취소 (불 켜기)
        remote.pressUndo();  // 불 좀 켜줄래?
    }
}

커맨드 패턴의 동작 원리

  1. Command 객체는 수행할 작업을 캡슐화합니다.
  2. **Receiver(수행 객체)**는 실제 작업을 수행합니다.
  3. **Invoker(요청자)**는 명령 객체를 받아서 execute() 메서드를 호출합니다.
  4. 명령 객체는 실행 취소(Undo) 기능을 제공할 수도 있습니다.

커맨드 패턴의 장단점

장점

  • 결합도 감소: 요청자와 수행 객체가 직접적으로 결합되지 않습니다.
  • 유연한 요청 처리: 명령 객체를 큐에 저장하거나 실행 취소 기능을 쉽게 구현할 수 있습니다.
  • 확장성 향상: 새로운 명령을 추가할 때 기존 코드를 수정할 필요가 없습니다.

단점

  • 클래스 증가: 각 명령에 대해 별도의 클래스가 필요하므로 코드가 길어지고 클래스 수가 증가할 수 있습니다.
  • 복잡성 증가: 작은 애플리케이션에서는 오히려 복잡성을 증가시킬 수 있습니다.

결론

커맨드 패턴은 요청을 객체로 캡슐화하여 결합도를 줄이고, 요청의 저장과 실행 취소 기능을 쉽게 구현할 수 있는 강력한 패턴입니다. 특히, 버튼 클릭 이벤트 처리, 트랜잭션 관리, 스마트 홈 시스템 등에서 자주 사용됩니다.

다만, 각 명령에 대해 별도의 클래스가 필요하기 때문에 코드가 복잡해질 수 있습니다. 따라서 규모가 큰 애플리케이션에서 주로 사용하며, 작은 시스템에서는 필요하지 않을 수 있습니다.