책너두 (헤드 퍼스트 디자인 패턴) 17일차 (~239p)

요약

  • 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() 같은 메소드가 호출되면서 리모컨에서 어떤 객체가 무슨 일을 하는지는 모르지만, 불이 켜진다거나 하는 식으로 필요한 일이 처리될 수 있음.

객체마을 식당과 커맨드 패턴

  1. 클라이언트 (고객)
    1. 커맨드 객체를 생성해야 함.
    2. 커맨드 객체는 리시버에 전달할 일련의 행동으로 구성됨.
  2. 리시버 (주방장)
    1. 커맨드 객체에는 행동과 리시버의 정보가 같이 들어 있음.
  3. 커맨드 (주문서)
    1. 커맨드 객체에서 제공하는 메소드는 execute() 하나 뿐임.
    2. 이 메소드는 행동을 캡슐화하여 리시버에 있는 특정 행동을 처리함.
  4. 인보커 (종업원)
    1. 클라이언트는 인보커 객체의 setCommand() 메소드를 호출함
    2. 이때, 커맨드 객체를 넘겨줌. → 나중에 쓰이기 전까지 인보커 객체에 보관됨.
  • 인보커에서 커맨드 객체의 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 객체를 생성함.
    • 커맨드 객체를 생성할 때, 이 리시버를 전달해 줌.
  • 커맨드 객체를 인보커에 전달하고, 버튼을 누름.

댓글

Designed by JB FACTORY