커맨드 패턴(Command Pattern)을 사용하면 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있습니다. 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있습니다.
IoT 리모컨을 만들어보자
리모컨은 프로그래밍이 가능한 7개의 슬롯과 각 슬롯에 할당된 기능을 켜고 끄는 ON/OFF 스위치가 있습니다. 각 슬롯은 서로 다른 가정용 기기에 연결할 수 있고 작업 취소 버튼도 장착되어 있습니다.
여러 가정용 기기 협력 업체로부터 자바 코드를 받아 보니 업체별로 사용하는 메소드가 다릅니다. 당연히 공통적인 인터페이스도 없는 상황입니다. 또한 앞으로도 이런 업체들이 계속 추가될 예정이라고 합니다. 코드를 어떻게 구현해야 할까요? (Ex. 조명 : 켜짐/꺼짐, TV : 켜짐/꺼짐/채널 변경/볼륨 설정, ...등등)
리모컨의 각 슬롯마다 업체별 기기 코드를 그대로 붙이게 되면 새로운 업체가 추가될 때마다 리모컨 클래스를 고쳐야 하고 버그 발생율도 올라갑니다. 이때 커맨드 패턴을 사용하여 해결할 수 있다고 합니다. 커맨드 패턴을 알아봅시다.
커맨드 패턴(Command Pattern)
위 그림에 대해 설명하자면
주문서는 주문 내용을 캡슐화한다. 따라서 종업원은 어떤 메뉴가 주문되었는지, 누가 식사를 준비할 건지 등을 전혀 몰라도 된다.
종업원은 주문서를 받고 orderUp() 메소드를 호출한다. 종업원의 takeOrder() 메소드에는 여러 고객의 주문서를 매개변수로 전달한다. 하지만 주문이 많아도 별로 어려울 것은 없다. orderUp() 메소드만 호출하면 식사가 준비되기 때문이다.
주방장은 식사를 준비하는 데 필요한 정보를 가지고 있다. 여기서 주방장과 종업원은 완전히 분리되어 있다.
결국 우리가 원하는건 리모컨 버튼이 눌렸을 때 호출되는 코드와 실제로 일을 처리하는 코드를 분리하는 것입니다. 위 그림에 커맨드 패턴을 대입해봅시다.
클라이언트(고객)는 커맨드 객체(주문서)를 생성해야 한다.
클라이언트는 인보커 객체(종업원)의 setCommand() 메소드를 통해 커맨드 객체를 넘겨주고, 이 커맨드 객체는 쓰이기 전까지 인보커 객체에 보관된다.
인보커에서 커맨드 객체의 excute() 메소드(orderUp())를 호출하면 리시버(주방장)에 있는 행동 메소드가 호출된다.
코드 구현
커맨드 인터페이스 구현 (주문서 용지) 커맨드 객체는 모두 같은 인터페이스를 구현해야 합니다. 그리고 그 인터페이스에는 메소드가 하나밖에 없습니다.
publicclassSimpleRemoteControl {Command slot; // 커맨드를 저장할 슬롯이 1개 있다.publicSimpleRemoteControl() {}publicvoidsetCommand(Command command) { // 슬롯을 가지고 제어할 명령을 설정, 얼마든지 기능을 변경할 수 있다. slot = command; }publicvoidbuttonWasPressed() {slot.execute(); }}
테스트 진행
publicclassRemoteControlTest {publicstaticvoidmain(String[] args) {SimpleRemoteControl remote =newSimpleRemoteControl(); // 인보커(리모컨)Light light =newLight(); // 리시버(가정용 기기)LightOnCommand lightOn =newLightOnCommand(light); // 커맨드(리시버 정보)remote.setCommand(lightOn); // 인보커에 커맨드(주문서) 할당remote.buttonWasPressed(); // 커맨드의 execute() 명령어 사용 }}// 불이 켜집니다.
커맨드 객체는 일련의 행동을 특정 리시버와 연결함으로써 요청을 캡슐화한 것입니다. 이러려면 행동과 리시버를 한 객체에 넣고, execute()라는 메소드 하나만 외부에 공개하는 방법을 써야 합니다. 밖에서 볼 때는 어떤 객체가 리시버 역할을 하는지, 그 리시버가 어떤 일을 하는지 알 수 없습니다.