텍스트 편집기에서 Ctrl+Z를 누르면 방금 한 작업이 취소된다. 그리고 Ctrl+Y를 누르면 다시 실행된다. 이 기능을 구현하려면 "방금 한 작업"을 어딘가에 기록해둬야 한다. 작업이 무엇인지, 어떻게 되돌리는지도 함께.
Command 패턴은 요청(작업) 자체를 객체로 만드는 패턴이다.
메서드 호출을 객체로 캡슐화한다. 요청을 보내는 쪽(Invoker)과 실제로 처리하는 쪽(Receiver)을 분리하고, 그 사이에 Command 객체가 자리한다.
요청이 객체가 되면 저장하거나, 큐에 넣거나, 실행 취소(undo)하는 것이 가능해진다.

텍스트 편집기의 글자 추가/삭제를 예로 든다.
// Command 인터페이스
public interface Command {
void execute();
void undo();
}
// Receiver — 실제 작업을 수행하는 객체
public class TextEditor {
private StringBuilder text = new StringBuilder();
public void addText(String content) {
text.append(content);
System.out.println("현재 텍스트: " + text);
}
public void removeText(int length) {
int start = text.length() - length;
if (start >= 0) {
text.delete(start, text.length());
}
System.out.println("현재 텍스트: " + text);
}
public String getText() {
return text.toString();
}
}
// 구체 Command — 글자 추가
public class AddTextCommand implements Command {
private TextEditor editor;
private String content;
public AddTextCommand(TextEditor editor, String content) {
this.editor = editor;
this.content = content;
}
@Override
public void execute() {
editor.addText(content);
}
@Override
public void undo() {
editor.removeText(content.length());
}
}
// Invoker — 커맨드를 실행하고 이력을 관리
public class CommandHistory {
private Deque<Command> history = new ArrayDeque<>();
public void execute(Command command) {
command.execute();
history.push(command);
}
public void undo() {
if (!history.isEmpty()) {
Command last = history.pop();
last.undo();
}
}
}
TextEditor editor = new TextEditor();
CommandHistory invoker = new CommandHistory();
invoker.execute(new AddTextCommand(editor, "Hello"));
// 현재 텍스트: Hello
invoker.execute(new AddTextCommand(editor, ", World"));
// 현재 텍스트: Hello, World
invoker.undo();
// 현재 텍스트: Hello
invoker.undo();
// 현재 텍스트: (빈 문자열)
Command가 객체이기 때문에 큐에 담아두고 나중에 일괄 실행하는 것도 자연스럽다.
Queue<Command> commandQueue = new LinkedList<>();
commandQueue.add(new AddTextCommand(editor, "첫 번째 작업"));
commandQueue.add(new AddTextCommand(editor, " 두 번째 작업"));
commandQueue.add(new AddTextCommand(editor, " 세 번째 작업"));
// 일괄 실행
while (!commandQueue.isEmpty()) {
commandQueue.poll().execute();
}
작업 예약, 배치 처리, 트랜잭션 로그 등에 이 방식이 활용된다.
Command 패턴의 출발점은 단순하다. "작업을 지금 당장 실행하는 대신, 객체로 만들어두면 뭘 할 수 있을까?" 거기서부터 실행 취소, 큐잉, 로깅 같은 기능들이 자연스럽게 따라온다.