[디자인패턴 수업 11주차 2차시] - Undo 기능과 메크로 커멘드 클래스

Jin Hur·2021년 11월 15일

Undo 기능

커멘드에서 작업 취소 기능을 지원하려면 execute() 메서드와 비슷한 undo() 메서드가 있어야 한다. execute() 메서드에서 했던 것과 정반대의 작업을 처리하면 된다.

public interface Command {
    public void execute();
    public void undo();
}

LightOnCommand 클래스에 undo 메서드 구현

public class LightOnCommand implements Command{

    // execute() 메서드가 호출되면 light 객체가 바로 그 요청에 대한 리시버(reciever)가 된다.
    Light light;

    // 생성자에서 이 커멘드 객체로 제어할 특정 전등(ex, 거실 전등)에 대한 정보가 전달됨.
    public LightOnCommand(Light light){
        this.light = light;
    }

    // execute() 메서드는 리시버 객체에 있는 on() 메서드를 호출
    @Override
    public void execute() {
        light.on();
    }
    
    // undo() 메서드에서는 반대 행동인 off() 메서드를 호출하도록 한다.
    @Override
    public void undo() {
        light.off();
    }
}

LightOffCommand 클래스에 undo 메서드 구현

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

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        light.on();
    }
}

RemoteControl 클래스를 약간만 고쳐 작업취소(undo) 기능을 추가할 수 있다.
마지막으로 실행된 명령을 기록하기 위한 인스턴스 변수를 추가하고, UNDO 버튼을 누르면 기록해두었던 커멘드 객체 레퍼런스를 이용해서 undo() 호출수 있다.

public class RemoteControl {
    // 하나 이상의 명령을 담을 수 있다.
    Command[] onCommands;   // on 계열 명령들
    Command[] offCommands;  // off 계역 명령들
    Command undoCommand;    // undo 버튼이 눌렸을 때를 대비하여 마지막으로 사용한 커멘드 객체를 집어넣을 변수

    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;
    }

    // 명령을 등록할 때 사용하는 setCommand 메서드
    public void setCommand(int slot, Command onCommand, Command offCommand){
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onButtonPushed(int slot){
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }
    public void offButtonPushed(int slot){
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }
    
    // 작업취소 기능 호출
    public void undoButtonWasPushed(){
        undoCommand.undo();
    }
}

undo 기능을 테스트 해보자.

public class RemoteLoader {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();

        Light livingRoomLight = new Light("Living Room Light");

        LightOnCommand lightOnCommand = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOffCommand = new LightOffCommand(livingRoomLight);

        // on, off 커멘드 각 슬롯 0번에 거실 불 on, off 기능을 넣는다.(명령 셋팅)
        remoteControl.setCommand(0, lightOnCommand, lightOffCommand);

        remoteControl.onButtonPushed(0);
        remoteControl.offButtonPushed(0);

        // undo test
        System.out.println("===== undo =====");
        remoteControl.undoButtonWasPushed();
    }
}


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

약간은 복잡한 CeilingFan 리시버 클래스

public class CelingFan {
    // 상태
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;

    String location;
    int speed;

    public CelingFan(String location){
        this.location = location;
    }
    public void high(){
        speed = HIGH;
    }
    public void medium(){
        speed = MEDIUM;
    }
    public void low(){
        speed = LOW;
    }
    public int getSpeed(){
        return speed;
    }
}

이 선풍기 명령어에 작업취소 기능 추가하기 => 이전 상태로 되돌린다!

public class CeilingFanHighCommand implements Command{
    CeilingFan ceilingFan;
    int prevSpeed;

    public CeilingFanHighCommand(CeilingFan ceilingFan){
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();  // 이전 상태를 저장
        ceilingFan.high();
    }

    @Override
    public void undo() {
        if(prevSpeed == CeilingFan.HIGH)
            ceilingFan.high();
        else if(prevSpeed == CeilingFan.MEDIUM)
            ceilingFan.medium();
        else if(prevSpeed == CeilingFan.LOW)
            ceilingFan.low();
        else
            ceilingFan.off();
    }
}

여러 명령들을 한번에 처리할 수 있는 메크로 커멘드 클래스

public class MacroCommand implements Command{
    Command[] commands;
    
    public MacroCommand(Command[] commands){
        this.commands = commands;
    }

    @Override
    public void execute() {
        for(int i=0; i<commands.length; i++){
            // 리모콘에서 메크로를 실행시키면 각 커멘드들을 순서대로 실행시킴.
            commands[i].execute();
        }
    }

    @Override
    public void undo() {
        for(int i=0; i<commands.length; i++){
            commands[i].execute();
        }
    }
}
public class RemoteLoader {
    public static void main(String[] args) {

        // ... 
        // ...
        
        // 메크로 커멘드 test
        Light light01 = new Light("Light 01");
        GarageDoor garageDoor01 = new GarageDoor("Garage Door 01");
        Stereo stereo01 = new Stereo("Stereo 01");

        // on commands
        LightOnCommand lightOn = new LightOnCommand(light01);
        GarageDoorOpenCommand doorOpenCommand = new GarageDoorOpenCommand(garageDoor01);
        StereoOnWithCDCommand stereoOnWithCDCommand = new StereoOnWithCDCommand(stereo01);
        // off commands
        LightOffCommand lightOff = new LightOffCommand(light01);
        GarageDoorOffCommand doorOffCommand = new GarageDoorOffCommand(garageDoor01);
        StereoOffWithCDCommand stereoOffWithCDCommand = new StereoOffWithCDCommand(stereo01);


        Command[] onCommands = {lightOn, doorOpenCommand, stereoOnWithCDCommand};
        Command[] offCommands = {lightOff, doorOffCommand, stereoOffWithCDCommand};

        MacroCommand onMacro = new MacroCommand(onCommands);
        MacroCommand offMacro = new MacroCommand(offCommands);

        remoteControl.setCommand(0, onMacro, offMacro);

        System.out.println("===== macro test");
        remoteControl.onButtonPushed(0);
        remoteControl.offButtonPushed(0);
    }
}

0개의 댓글