Conceptly
← 전체 목록
📋

Command

행동요청을 객체로 캡슐화해 실행·취소·큐잉을 가능하게 하는 행동 패턴

Command 패턴은 '이것을 해라'라는 요청 자체를 하나의 객체로 만드는 구조입니다. 요청을 보내는 쪽(Invoker)은 그 요청이 구체적으로 무엇을 어떻게 하는지 모르고, 실제 작업을 수행하는 쪽(Receiver)은 누가 요청했는지 모릅니다. 요청이 객체가 되면 저장, 전달, 취소, 재실행이 가능해집니다.

아키텍처 다이어그램

🔄 프로세스 다이어그램

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

버튼을 누르면 동작이 실행되는 GUI를 만든다고 할 때, 버튼 코드 안에 직접 동작을 작성하면 같은 동작을 메뉴, 단축키, 컨텍스트 메뉴에서도 호출하고 싶을 때 코드가 중복됩니다. 여기에 '되돌리기' 기능이 필요해지면 문제가 커집니다. 어떤 동작이 언제 실행됐는지를 기억해야 하고, 그 동작을 거꾸로 돌릴 방법도 함께 가지고 있어야 합니다. 동작이 함수 호출로 흩어져 있으면 실행 이력을 관리할 대상 자체가 없습니다.

왜 이런 방식이 등장했나요?

Command 패턴의 뿌리는 1970년대 운영체제의 작업 스케줄링까지 거슬러 올라갑니다. 작업을 데이터 구조로 표현하면 큐에 넣고, 우선순위를 매기고, 로그를 남길 수 있었습니다. GUI 시대가 오면서 이 아이디어가 애플리케이션 레벨로 내려왔습니다. 텍스트 편집기에서 Undo/Redo를 구현하려면 사용자의 각 동작을 되돌릴 수 있는 형태로 기록해야 했고, 그 기록 단위가 곧 Command 객체가 된 것입니다. GoF가 패턴으로 정리한 이후로 이 구조는 메시지 큐, CQRS(Command Query Responsibility Segregation), 이벤트 소싱 같은 아키텍처 패턴에서도 핵심 빌딩 블록으로 쓰이고 있습니다.

내부적으로 어떻게 동작하나요?

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를 도입할 이유는 없습니다.

Undo/Redo트랜잭션매크로작업 큐