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

Jin Hur·2021년 11월 15일
0
post-custom-banner

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

post-custom-banner

0개의 댓글