요약
- 호출 캡슐화하기 : 커맨드 패턴
- 커맨드 패턴의 정의
- 커맨드 패턴 클래스 다이어그램 살펴보기
- 슬롯에 명령 할당하기
- 리모컨 코드 만들기
- 커맨드 클래스 만들기
- 리모컨 테스트
- 작업 취소 기능 추가하기
- 작업 취소 기능 테스트
- 작업 취소 기능을 구현할 때 상태를 사용하는 방법
 
메모
커맨드 패턴의 정의
📍 커맨드 패턴(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 를 기억하기 위한 인스턴스 변수만 관리하면 됨.
작업 취소 기능 테스트
작업 취소 기능을 구현할 때 상태를 사용하는 방법
- ex) 선풍기 속도
- 이전 선풍기 속도의 상태를 인스턴스변수로 가지고 있어야 함. (prevSpeed)
- 이전 속도를 알고 있으면 작업 취소를 해도, 이전 속도로 돌아갈 수 있음.
 
- p255~256 참고