Command 패턴

요청을 객체로 캡슐화해 실행·취소·재실행·큐잉이 가능하도록 하는 행동 디자인 패턴입니다.

구조

Client ──→ Invoker ──→ Command (interface)
                           ├── execute()
                           └── undo()
                              │
                    ┌─────────┴──────────┐
               ConcreteCommandA    ConcreteCommandB
                    │                    │
                 Receiver             Receiver

구현 — Undo/Redo

// Command 인터페이스
interface Command {
    void execute();
    void undo();
}

// ConcreteCommand: 텍스트 삽입
class InsertCommand implements Command {
    private final StringBuilder text;
    private final int position;
    private final String value;

    InsertCommand(StringBuilder text, int position, String value) {
        this.text = text;
        this.position = position;
        this.value = value;
    }

    @Override public void execute() { text.insert(position, value); }
    @Override public void undo()    { text.delete(position, position + value.length()); }
}

// Invoker: Undo/Redo 히스토리 관리
class CommandHistory {
    private final Deque<Command> undoStack = new ArrayDeque<>();
    private final Deque<Command> redoStack = new ArrayDeque<>();

    void execute(Command cmd) {
        cmd.execute();
        undoStack.push(cmd);
        redoStack.clear();     // 새 커맨드 실행 시 redo 히스토리 초기화
    }

    void undo() {
        if (!undoStack.isEmpty()) {
            Command cmd = undoStack.pop();
            cmd.undo();
            redoStack.push(cmd);
        }
    }

    void redo() {
        if (!redoStack.isEmpty()) {
            Command cmd = redoStack.pop();
            cmd.execute();
            undoStack.push(cmd);
        }
    }
}

사용 예

StringBuilder doc = new StringBuilder("Hello");
CommandHistory history = new CommandHistory();

history.execute(new InsertCommand(doc, 5, " World"));  // "Hello World"
history.execute(new InsertCommand(doc, 11, "!"));      // "Hello World!"

history.undo();   // "Hello World"
history.undo();   // "Hello"
history.redo();   // "Hello World"

언제 사용하나

장단점

내용
장점실행·취소 로직 분리, OCP 준수 (새 커맨드 추가 시 기존 코드 변경 없음)
단점커맨드 클래스 수 증가, 간단한 경우 오버엔지니어링

핵심 정리

→ SOLID

CSLOG에서 보기