Command
Command 패턴은 '이것을 해라'라는 요청 자체를 하나의 객체로 만드는 구조입니다. 요청을 보내는 쪽(Invoker)은 그 요청이 구체적으로 무엇을 어떻게 하는지 모르고, 실제 작업을 수행하는 쪽(Receiver)은 누가 요청했는지 모릅니다. 요청이 객체가 되면 저장, 전달, 취소, 재실행이 가능해집니다.
▶아키텍처 다이어그램
🔄 프로세스 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
버튼을 누르면 동작이 실행되는 GUI를 만든다고 할 때, 버튼 코드 안에 직접 동작을 작성하면 같은 동작을 메뉴, 단축키, 컨텍스트 메뉴에서도 호출하고 싶을 때 코드가 중복됩니다. 여기에 '되돌리기' 기능이 필요해지면 문제가 커집니다. 어떤 동작이 언제 실행됐는지를 기억해야 하고, 그 동작을 거꾸로 돌릴 방법도 함께 가지고 있어야 합니다. 동작이 함수 호출로 흩어져 있으면 실행 이력을 관리할 대상 자체가 없습니다.
초기 GUI와 작업 처리 시스템에서는 같은 동작을 버튼, 메뉴, 단축키, 큐 같은 여러 진입점에서 재사용해야 했습니다. 그런데 동작이 단순 함수 호출로 흩어져 있으면 어떤 요청이 언제 실행됐는지 저장할 수 없었고, 나중에 다시 실행하거나 되돌리는 것도 어려웠습니다. '요청 자체를 데이터처럼 다뤄 큐에 넣고, 기록하고, 되돌릴 수 있어야 한다'는 압력 속에서 Command가 정리됐습니다. 이후 메시지 큐나 작업 스케줄링으로 확장됐지만, 출발점은 요청의 생명주기를 메서드 호출 바깥으로 끌어내는 데 있습니다.
Command 패턴에는 네 역할이 있습니다. Command 인터페이스는 execute()를 정의합니다. 구체 Command는 이 인터페이스를 구현하면서 내부에 Receiver 참조와 실행에 필요한 파라미터를 갖습니다. Invoker는 Command 객체를 받아 저장하고 있다가 적절한 시점에 execute()를 호출합니다. Receiver는 실제 비즈니스 로직을 수행합니다. 흐름은 이렇습니다. Client가 Receiver를 만들고, 그 Receiver를 포함하는 Command 객체를 생성해 Invoker에 등록합니다. Invoker는 트리거(버튼 클릭, 스케줄, 이벤트 등)가 발생하면 Command의 execute()를 호출하고, Command는 내부의 Receiver에게 실제 작업을 위임합니다. Undo를 지원하려면 Command에 undo() 메서드를 추가하고, Invoker가 실행된 Command를 스택에 쌓습니다. 되돌리기 요청이 오면 스택에서 꺼내 undo()를 호출합니다. 이 구조 덕분에 실행 이력 관리, 매크로(여러 Command를 하나로 묶기), 트랜잭션(전부 성공하거나 전부 롤백) 같은 확장이 자연스러워집니다.
Command와 Strategy는 둘 다 행동을 객체로 감싸지만, 목적이 다릅니다. Strategy는 '같은 작업을 다른 방식으로 수행'하기 위해 알고리즘을 교체하는 것이고, Command는 '어떤 작업을 했는지 기록하고 제어'하기 위해 요청을 객체화하는 것입니다. Strategy는 알고리즘 교체에 관심이 있고, Command는 실행 시점·이력·취소에 관심이 있습니다. Observer와도 혼동될 수 있습니다. Observer는 상태 변경을 여러 곳에 전파하는 구조인 반면, Command는 하나의 요청을 객체로 만들어 그 요청의 생명주기(생성, 전달, 실행, 취소)를 관리하는 구조입니다. 이벤트가 발생하면 Observer로 전파하고, 전파된 핸들러 안에서 Command 객체를 실행하는 식으로 함께 쓰이기도 합니다.
Command 패턴이 가장 빛나는 장면은 Undo/Redo입니다. 텍스트 편집기, 그래픽 에디터, 스프레드시트 같은 도구에서 사용자의 모든 조작을 Command로 기록하면, 실행 취소는 스택을 되감는 것만으로 구현됩니다. 작업 큐에서도 유용합니다. 요청을 Command 객체로 직렬화하면 네트워크를 통해 다른 서버로 보내거나, 큐에 넣어두고 나중에 워커가 처리할 수 있습니다. 트랜잭션 경계가 필요한 상황에서는 여러 Command를 묶어 전부 execute() 하거나, 하나라도 실패하면 전부 undo() 하는 식으로 원자성을 구현합니다. 도입 신호는 '이 동작을 되돌릴 수 있어야 한다', '이 요청을 나중에 실행해야 한다', '이 동작의 이력을 기록해야 한다' 같은 요구입니다. 단순히 메서드를 호출하는 것만으로 충분하고 이력이나 취소가 필요 없다면 Command를 도입할 이유는 없습니다.