자바 디자인 패턴 용어 사전
행위 패턴커맨드 · 요청의 객체화

Command

요청을 객체로 만들어 실행을 미루거나 되돌릴 수 있게 하는 패턴.

요청 자체를 객체로 만드는 패턴. 실행을 미루거나 기록하거나 되돌릴 수 있게 된다.

interface Command {
    void execute();
    void undo();
}

class CopyCommand implements Command {
    private final Editor editor;
    private String backup;

    public void execute() { backup = editor.getText(); editor.copy(); }
    public void undo() { editor.setText(backup); }
}
// 실행 이력을 스택에 쌓으면 실행 취소가 된다
Deque<Command> history = new ArrayDeque<>();

void run(Command c) { c.execute(); history.push(c); }
void undo() { if (!history.isEmpty()) history.pop().undo(); }

무엇을 가능하게 하나

  • 실행 취소 / 다시 실행
  • 요청을 큐에 넣어 나중에 실행 (작업 큐)
  • 요청을 로그로 남겨 재실행 (장애 복구)
  • 매크로 — 커맨드를 묶어 하나처럼 실행 (컴포지트와 결합)
  • 호출자와 수신자를 분리 (버튼이 무엇이 실행될지 몰라도 된다)
// 같은 커맨드를 버튼·단축키·메뉴가 공유한다
button.setCommand(new CopyCommand(editor));
shortcut.setCommand(new CopyCommand(editor));

Memento와 짝을 이룬다

// 상태 전체를 스냅샷으로 저장했다 복원한다
class Editor {
    Memento save() { return new Memento(text, cursor); }
    void restore(Memento m) { this.text = m.text; this.cursor = m.cursor; }
}

Command 의 undo 는 '역연산' 을 구현한다 (되돌리는 방법을 안다) Memento 는 '이전 상태' 를 통째로 저장한다 (되돌리는 방법을 몰라도 된다)

역연산을 정의하기 어려우면 Memento 가 낫다 (대신 메모리를 더 쓴다)

이벤트 소싱과의 관계

상태를 저장하는 대신 '일어난 명령/사건' 을 순서대로 저장한다
  → 처음부터 재생하면 현재 상태가 나온다
  → 과거 어느 시점으로도 되돌릴 수 있다
  → Command 를 영속화한 것과 같은 발상이다

비용

  • 명령 하나마다 클래스가 하나씩 는다
  • 단순한 호출을 객체로 감싸는 간접층이 생긴다

실행 취소 · 큐잉 · 로깅 중 하나라도 필요할 때 값을 한다 그냥 메서드를 부르면 되는 곳에 쓰면 순수한 복잡도다

면접 함정

  • "Command는 실행 취소를 위한 것" → 그건 응용 중 하나다. 본질은 요청의 객체화다.
  • "람다가 있으니 Command는 불필요" → 실행 취소·직렬화·이력이 필요하면 여전히 객체여야 한다.

매크로 — 컴포지트와 결합

class MacroCommand implements Command {
    private final List<Command> commands;
    public void execute() { commands.forEach(Command::execute); }
    public void undo() {
        // 역순으로 되돌린다 — 순서가 중요하다
        for (int i = commands.size() - 1; i >= 0; i--) commands.get(i).undo();
    }
}

되돌릴 수 없는 명령이 섞이면

  • 메일 발송 · 결제 승인처럼 되돌릴 수 없는 명령이 있다

  • ① 그런 명령은 undo() 에서 예외를 던지게 하고 UI 에서 막는다

  • ② 보상 동작을 정의한다 (결제 취소 · 정정 메일)

  • ③ 되돌릴 수 없는 명령을 맨 뒤에 배치한다

  • 셋 중 무엇을 택할지는 업무가 정한다

큐잉과 직렬화

// 커맨드를 직렬화하면 프로세스를 넘어 실행할 수 있다
queue.send(new ProcessOrderCommand(orderId));

// 이 시점부터 커맨드는 '메시지' 와 사실상 같은 것이 된다
// → 메시지 큐 기반 아키텍처가 이 패턴의 분산 버전이다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 행위 패턴 ① — 요청을 객체로 다룬다