커맨드 패턴은 매우 단순하면서도 사용 범위가 매우 넓은 패턴이다. 커맨드 패턴은 그저 인터페이스 1개로 이루어져있다.
public interface Command {
public void do();
}
일반적인 클래스들은 메서드와 그에 대응하는 변수 집합을 결합한다. 하지만 커맨드 패턴의 경우에는 함수를 캡슐화해서 변수에서 해방시킨다.
Command 인터페이스를 다양한 객체들이 구현하게 함으로써 시스템에 Command객체를 넘겨주는 것만으로, 그것이 어떤 Command를 표현하는지 몰라도 do()를 실행할 수 있다. 이것은 구조를 단순하게 만들어주는 장점이 있다. 예를 들어 센서를 통해서 그에 따른 적절한 업무를 실행시켜야한다고 할 때, 의존성 방향은 아래처럼 설정해주면 된다.
Sensor -> Command
Sensor는 그저 어떤 이벤트를 탐지하면 의존하고 있는 Command의 do()를 호출할 뿐이다. Sensor가 자신이 구체적으로 어떤 Command를 호출하는지 알 필요가 없다. 이로 인해, Sensor의 함수도 단순해질 것이고, 위에 보이는 것처럼 구조 또한 매우 단순하게 가져갈 수 있다.
Sensor가 이벤트를 알렸을 때 어떤 Command를 실행할지 결정하는 문제들은 초기화 함수에서 처리하면 된다. 시스템을 초기화할 때, 각 Sensor는 적절한 Command와 묶이게 될 것이다. 모든 논리적 상호연결을 한 곳에 넣고 시스템의 중심 부분과 분리시킬 수 있는 것이다. 결과적으로 명령의 개념을 커맨드 패턴을 이용하여 캡슐화한 것으로 시스템의 논리적인 상호 연결 부분을 따로 분리할 수 있게 되었다.
- 책 내에서의 예제에서 커맨드 패턴은 시스템이 정보에 따라 동작하기 전에 그 정보가 문법적으로나 의미적으로 옳은지 검증하는 것을 돕는다.
- Command객체는 총 3가지의 역할을 한다.
1. 검증되지 않은 데이터를 위한 저장소 역할
- 검증 메서드를 구현
- 트랜잭션을 실행하는 메서드를 구현\
- 아래는 책 내에서의 커맨드 패턴을 적용한 Transaction 인터페이스의 예시이다.
public interface Transaction { public void validate(); // 검증 public void execute(); // 데이터베이스 갱신 }
- 책 내 예시에서 이처럼 커맨트 패턴을 사용했을 때의 장점은 사용자에게서 데이터를 받는 코드, 데이터를 검증하고 그것으로 작업하는 코드, 업무 객체를 전부 서로 분리할 수 있다는 것이다.
- 이런 분리를 도메인 로직을 입력 인터페이스에서 물리적으로 분리했다고 하여 물리적 분리라고 한다.
- 명령의 입력을 받은 후, 그 시점에서 명령이 옳은지 검증만 하고, 나중에 시간 간격을 두어 검증된 명령들만 수행할 수도 있다. 커맨드 패턴으로 이것을 실현시킬 수 있는데, 이것을 시간적 분리라고 한다.
액티브 오브젝트 패턴은 커맨드 패턴 응용 방식으로 다중 제어 스레드 구현을 위한 기법이기도 하다. ActiveObjectEngine 객체가 Command 객체의 연결 리스트를 유지하는 하는 형태이다.
public class ActiveObjectEngine {
LinkedList<Command> itsCommands = new LinkedList<Command>();
public void addCommand(Command c) {
itsCommands.add(c);
}
public void run() throws Exception {
while(!itsCommands.isEmpty()) {
Command c = itsCommands.getFirst();
itsCommands.removeFirst();
c.execute();
}
}
}
public interface Command {
public void execute() throws Exception;
}
이 패턴을 응용하여 이벤트를 기다리는 멀티스레드 프로그램과 유사한 프로그램을 만들 수 있다. 하지만 전통적인 멀티스레드 시스템과 다른 점은 이 패턴을 응용하여 만든 이 RTC(Run-To-Completion) 스레드는 모두 같은 런타임 스택을 공유한다. 그렇기 때문에 RTC 스레드에 대해 별도의 런타임 스택을 정의하거나 할당할 필요가 없다. 그렇기에 많은 스레드가 실행되고 메모리가 제한된 시스템에서 강력한 이점을 가질 수 있다. (자세한 구현 방법은 책에 예시로 나와있다.)
커맨드 패턴의 유연성과 단순성은 매우 큰 장점이다. 설명한 트랜잭션/장치 제어/멀티 스레드 외에도 책에 있는 실행/취소까지 정말 다양하게 사용 가능하다. 물론 이러한 커맨드 패턴은 클래스보다 함수 그 자체에 집중하는 느낌이라서 객체 지향 패러다임을 망가뜨린다고 생각되기도 하지만, 개발자는 이 커맨드 패턴을 실제 현장에서 아주 유용하게 사용할 수 있을 것 같다.