요약
- 6장. 호출 캡슐화하기 : 커맨드 패턴
- IOT 리모컨 API 제작
- 협력 업체 클래스 살펴보기
- 커맨드 패턴 소개
- 객체마을 식당 등장인물의 역할
- 객체마을 식당과 커맨드 패턴
- 클라이언트 (고객)
- 리시버 (주방장)
- 커맨드 (주문서)
- 인보커 (종업원)
- 첫 번째 커맨드 객체 만들기
- 커맨드 객체 사용하기
메모
CHAPTER 06. 호출 캡슐화하기 : 커맨드 패턴
만능 IOT 리모컨
- 프로그래밍이 가능한 7개의 슬롯 + 슬롯 별 ‘ON’, ‘OFF’ 버튼이 있음.
- 마지막을 누른 버튼의 명령을 취소하는 ‘UNDO’ 버튼이 있음.
- 각 슬롯을 한 가지 기기, 혹은 하나로 엮여 있는 일련의 기기에 할당할 수 있는 API 제작이 목표.
협력 업체 클래스 살펴보기
- OutdorrLight
- CeilingLight
- TV
- GardenLight,
- Sprinkler
- …
- 클래스가 정말 많은데, 공통적인 인터페이스는 없어 보임.
- 더 큰 문제는 이런 클래스가 더 추가될 수 있음.
- 이러한 클래스들은 저마다 공통 메서드가 아닌, 자신만의 메서드를 가지고 있어서 더 공통적으로 처리하기가 힘듦.
- 리모컨을 이용해서 작업을 처리하더라도 협력 업체의 클래스에 의존하게 될 것이고, 이는 좋지 않은 디자인임.
- 커맨드 패턴을 이용하면 커맨드 객체에게 요청에 대한 작업을 캡슐화함.
- 리모컨은 아무것도 몰라도 되고, 커맨드 객체에게 시키기만 하면 됨.
커맨드 패턴 소개
- 음식 주문 과정을 통해 커맨드 패턴을 살펴본다.
- 고객 - 웨이트리스 - 주문 - 주방장 → 사이의 관계를 살펴보면서 커맨드 패턴 속 각 객체 사이의 관계를 쉽게 이해할 수 있음.
- 고객 : 원하는 것을 주문 (createOrder) → 주문이 만들어 짐
- 종업원 : 주문을 가져와서 처리 요청함. (takeOrder, orderUp)
- 주방장 : 주문으로부터 전달받은 지시사항에 따라 음식을 주문함. (makeBurger, makeShake)
객체마을 식당 등장인물의 역할
주문서는 주문 내용을 캡슐화함.
- 주문서는 주문 내용을 요구하는 객체
- 이 객체의 인터페이스에는 식사 준비에 필요한 행동을 캡슐화한 orderUp() 메소드가 들어 있음.
- 식사를 주문해야하는 주방장 객체 레퍼런스도 들어 있음.
- 종업원은 어떤 메뉴가 주문되고, 누가 식사를 준비할 건지 전혀 몰라도, orderUp() 메소드만 호출하면 됨. (”주문 들어왔어요!” 라고 한 마디 말하는 것과 동일)
종업원은 주문서를 받고 orderUp() 메소드를 호출함.
- takeOrder 메소드를 통해 여러 고객의 주문서를 매개변수로 전달함.
- 종업원 입장에서는 그저 orderUp() 메소드만 호출하면 식사가 준비됨.
주방장은 식사를 준비하는 데 필요한 정보를 가지고 있음.
- 실제 식사를 준비하는 방법은 주방장만 암.
- 종업원이 orderUp() 메소드를 호출하면 주방장이 그 주문을 받아서 음식을 만들 때 필요한 메소드를 전부 처리함.
- 이때, 주방장과 종업원은 완전히 분리되어 있음.
- 리모컨 슬롯에 객체마을 식당 주문서 같은 객체가 들어오게 되면, 버튼을 눌렀을 때, orderUp() 같은 메소드가 호출되면서 리모컨에서 어떤 객체가 무슨 일을 하는지는 모르지만, 불이 켜진다거나 하는 식으로 필요한 일이 처리될 수 있음.
객체마을 식당과 커맨드 패턴
- 클라이언트 (고객)
- 커맨드 객체를 생성해야 함.
- 커맨드 객체는 리시버에 전달할 일련의 행동으로 구성됨.
- 리시버 (주방장)
- 커맨드 객체에는 행동과 리시버의 정보가 같이 들어 있음.
- 커맨드 (주문서)
- 커맨드 객체에서 제공하는 메소드는 execute() 하나 뿐임.
- 이 메소드는 행동을 캡슐화하여 리시버에 있는 특정 행동을 처리함.
- 인보커 (종업원)
- 클라이언트는 인보커 객체의 setCommand() 메소드를 호출함
- 이때, 커맨드 객체를 넘겨줌. → 나중에 쓰이기 전까지 인보커 객체에 보관됨.
- 인보커에서 커맨드 객체의 execute() 메소드를 호출하면 리시버에 있는 행동 메소드가 호출됨.
첫 번째 커맨드 객체 만들기
커맨드 인터페이스 구현
public interface Command {
public void execute();
}
- 커맨드 객체는 모두 같은 인터페이스를 구현해야 함.
- 그 인터페이스에는 메소드가 하나밖에 없음 → execute()
조명을 켤 때 필요한 커맨드 클래스 구현
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
- 생성자에 커맨드 객체로 제어할 특정 조명 정보가 전달됨.
- 그 객체는 light 라는 인스턴스 변수로 저장됨 → execute() 메소드가 호출되면 light 객체가 바로 그 요청의 리시버가 됨.
커맨드 객체 사용하기
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}
- 우선은, 제어할 기기를 연결할 슬롯, 버튼이 하나만 있다고 가정해보자.
- setCommand 메서드를 통해 제어할 명령을 설정함.
- 리모컨 버튼 기능을 바꾸고 싶으면 이 메서드를 호출하여 바꾸면 됨.
- 버튼을 누르면 buttonWasPressed() 메서드가 호출됨.
- 슬롯에 연결된 커맨드 객체의 execute() 메소드만 호출하면 됨.
리모컨을 사용할 때 필요한 간단한 테스트 클래스
public class RemoteControlTest {
public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}
- remote 변수가 인보커 역할을 함.
- 필요한 작업 요청 시, 사용할 커맨드 객체를 인자로 전달받음.
- 요청을 받아서 처리할 리시버인 Light 객체를 생성함.
- 커맨드 객체를 생성할 때, 이 리시버를 전달해 줌.
- 커맨드 객체를 인보커에 전달하고, 버튼을 누름.