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

요약

  • 호출 캡슐화하기 : 커맨드 패턴
    • 커맨드 패턴의 정의
    • 커맨드 패턴 클래스 다이어그램 살펴보기
    • 슬롯에 명령 할당하기
    • 리모컨 코드 만들기
    • 커맨드 클래스 만들기
    • 리모컨 테스트
    • 작업 취소 기능 추가하기
    • 작업 취소 기능 테스트
    • 작업 취소 기능을 구현할 때 상태를 사용하는 방법

메모

커맨드 패턴의 정의

📍 커맨드 패턴(Command Pattern) : 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있음. 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있음.
  • 메타 커맨드 패턴 → 여러 개의 명령을 매크로로 한 번에 실행할 수 있음.

커맨드 패턴 클래스 다이어그램 살펴보기

  • 클라이언트
    • ConcreteCommand를 생성하고, Receiver를 설정함
  • 인보커
    • 명령이 들어있음.
    • execute() 메소드를 호출해서 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 함
  • 커맨드 (인터페이스)
    • 모든 명령은 execute() 메소드 호출로 수행됨.
    • 리시버에 특정 작업을 처리하라는 지시를 전달함.
  • 리시버
    • 요구사항을 수행할 때 어떤 일을 처리해야 하는지 알고 있는 객체
  • 구상 커맨드
    • 특정 행동과 리시버를 연결해줌.
    • 인보커에서 execute() 호출하면, ConcreteCommand 객체에서 리시버에 있는 메소드를 호출해서 그 작업을 처리함.

슬롯에 명령 할당하기

  • 각 슬롯에 명령을 할당해서, 리모컨이 인보커가 되게 만든다.
    • 사용자가 버튼을 누르면 그 버튼에 맞는 커맨드 객체의 execute() 메소드가 호출되고, 리시버(조명, 선풍기, 오디오 등)에서 특정 행동을 담당하는 메소드가 실행됨.

리모컨 코드 만들기

public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    public void offButtonWasPushed (int slot) {
        offCommands[slot].execute();
    }

    public String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append("\n-2------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuff.append("[slot " + i + "]" + onCommands[i].getClass().getName()
            + "    " + offCommands[i].getClass().getName() + "\n");
        }
        return stringBuff.toString();
    }
}
  • 리모컨 코드는 7개의 ON/OFF 명령을 처리할 수 있음.
  • setCommand 메서드는 슬롯 번호와 그 슬롯에 저장할 ON, OFF 커맨드 객체를 인자로 받음
  • 사용자가 ON, OFF 버튼을 누르면 리모컨 하드웨어에서 각 버튼에 대응되는 버튼 메소드를 호출함.

커맨드 클래스 만들기

public class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.off();
    }
}
  • 조명 끌 때 쓰는 커맨드 클래스임.
public class StereoOnWithCDCommand implements Command {
    Stereo stereo;

    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}
  • 오디오를 켤때 쓰는 커맨드 클래스임.
  • 리시버에 따라 구현 로직은 조금 달라질 수 있음.

리모컨 테스트

  • p246~247 참고
  • RemoteControl 이 만들어 질 때, 초기 커맨드 값으로 NoCommand 값을 넣음.
    • 물론 특정 슬롯을 쓰려고 할 때마다, 어떤게 로딩되어 있는지 확인할 수 있지만 귀찮아서, 아무 일도 하지 않는 커맨드 클래스를 구현해서 넣은 것임.
  • NoCommand 객체는 일종의 널 객체(null object)임.
    • 널 객체는 리턴할 객체도 없고, 클라이언트가 null을 처리하지 않게 하고 싶을 때 처리하면 좋음.
    • 널 객체는 여러 디자인 패턴에서 유용하게 쓰임.
      • 그래서 널 객체를 일종의 디자인 패턴으로 분류하기도 함.

작업 취소 기능 추가하기

public interface Command {
    public void execute();
    public void undo();
}
  • Command 인터페이스에 undo 메소드를 추가함.
public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }

    public void undo() {
        light.off();
    }
}
  • execute 메소드는 불을 켜기 때문에 undo()는 불을 끄기만 하면 됨.
  • 반대로 LightOffCommand 는 undo 메소드에서 light.on() 해주기만 하면 됨.
public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    public void offButtonWasPushed (int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    public void undoButtonWasPushed() {
        undoCOmmand.undo();
    }

    public String toString() {
        // toString 코드
    }
}
  • RemoteControl 객체는 undoCommand 를 기억하기 위한 인스턴스 변수만 관리하면 됨.
    • on/off 버튼을 누를 때마다 기록함.

작업 취소 기능 테스트

  • p254 참고

작업 취소 기능을 구현할 때 상태를 사용하는 방법

  • ex) 선풍기 속도
    • 이전 선풍기 속도의 상태를 인스턴스변수로 가지고 있어야 함. (prevSpeed)
    • 이전 속도를 알고 있으면 작업 취소를 해도, 이전 속도로 돌아갈 수 있음.
  • p255~256 참고

댓글

Designed by JB FACTORY