[JAVA/디자인패턴] Command Pattern, 커맨드 패턴

2023. 11. 29. 09:47
반응형

 

Command Pattern

https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern, 상명대학교 조용주 교수님 <고급객체지향프로그래밍>

 

 

요구사항(요청, 명령)을 객체로 캡슐화시키는 디자인 패턴.

이를 이용해서 다른 요구사항을 지닌 클라이언트를 매개변수화 시킬 수 있고, 요구사항을 큐에 넣거나 로그로 남길 수 있으며 작업 취소(undo) 기능을 지원할 수도 있다.

 

CommandReceiver를 알고 있고, Receiver의 메소드를 호출, Receiver의 메소드에서 사용되는 파라미터들은 Command에 저장됨

Receiver: 실제 명령(command)을 수행한다.

Invoker: 요청을 받아서, 요청을 실행하기 위해 Command 인터페이스 연결, Command 가 실제 어떻게 실행되는지 모른다.

Client: 무엇을 요청할지 결정하고, 요청 CommandInvoker에 넘긴다.

 

 

 


STEP 1 : 조명을 켜고 끄는 리모컨

조명을 키고 끌 수 있는 명령을 할 수 있는 리모컨을 만들어 보도록 하자.

조명은 실제 행위를 하는 Receiver가 될 것이다.

키고 끄는 명령은 ConcreteCommand이고, execute()를 통해 Receiver을 행동하게 할 것이다. 

리모컨은 요청을 실행하는 Invoker의 역할을 할 것이다.

 

 

1. Command와 ConcreteCommand

 

public interface Command {
    void execute();
}
// Light를 끄는 명령 클래스
public class TurnOffLightCommand implements Command {
    private Light light;

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

    @Override
    public void execute() {
        light.turnOff();
    }
}
// Light를 켜는 명령 클래스
public class TurnOnLightCommand implements Command {
    private Light light;

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

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

 

명령을 나타내는 인터페이스 및 Concrete Class이다. execute() 메서드를 선언 및 구현하여 실제 객체(Receiver)에게 명령을 처리하도록 한다.

 

 

 

2. Receiver

// 실제 Light 객체 (Receiver)
public class Light {
    private boolean isOn = false;

    public void turnOn() {
        isOn = true;
        System.out.println("조명이 켜졌습니다.");
    }

    public void turnOff() {
        isOn = false;
        System.out.println("조명이 꺼졌습니다.");
    }

    public boolean isOn() {
        return isOn;
    }
}

 

명령의 실제 수행을 담당하는 객체로, ConcreteCommand에서 정의한 execute() 메서드를 통해 작동한다.

 

 

3. Invoker

// 리모컨(Invoker)
public class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

 

명령을 실행하는 주체. 명령을 실행하기 위해 Command 객체를 호출한다.

 

 

 

4. Client

public class Client {
    public static void main(String[] args) {
        Light light = new Light();
        TurnOnLightCommand turnOnCommand = new TurnOnLightCommand(light);
        TurnOffLightCommand turnOffCommand = new TurnOffLightCommand(light);

        RemoteControl remoteControl = new RemoteControl();

        // 버튼 누르면 조명 켜기
        remoteControl.setCommand(turnOnCommand);
        remoteControl.pressButton();

        // 버튼 누르면 조명 끄기
        remoteControl.setCommand(turnOffCommand);
        remoteControl.pressButton();
    }
}

 

클라이언트는 명령을 추가하고 실행하는데에만 관여한다. 명령의 구체적인 동작은 ConcreteCommand 클래스의 execute()에 의해 정의된다.

 

 


STEP 2 : Invoker에 여러개의 명령을 일괄 실행하도록 하기

 

// 리모컨(Invoker)
public class RemoteControl {
    private List<Command> commands = new ArrayList<>();

    public void setCommand(Command command) {
        this.commands.add(command);
    }

    public void pressAllButton() {
        for(Command command: commands){
            command.execute();
        }

    }
}

 

public class Client {
    public static void main(String[] args) {
        Light light = new Light();
        TurnOnLightCommand turnOnCommand = new TurnOnLightCommand(light);
        TurnOffLightCommand turnOffCommand = new TurnOffLightCommand(light);

        RemoteControl remoteControl = new RemoteControl();
        
        remoteControl.setCommand(turnOnCommand);
        remoteControl.setCommand(turnOffCommand);
        remoteControl.pressAllButton();
    }
}

 

setCommand 메서드를 통해 명령을 추가하고, pressAllButtons 메서드를 통해 저장된 모든 명령을 실행하고 있다.

마찬가지로 클라이언트는 명령을 추가하고 실행하는데에만 관여한다. 명령의 구체적인 동작은 ConcreteCommand 클래스의 execute()에 의해 정의된다.


STEP 3 : Command Pattern에 Undo 기능 포함하기

 

앞에서 요구사항을 큐에 넣거나 로그로 남길 수 있으며 작업 취소(undo) 기능을 지원할 수도 있다고 하였다. 이번에는 실행한 명령을 취소할 수 있는 기능을 만들어보겠다.

 

public interface Command {
    void execute();

    // Undo 기능을 위한 메서드
    void undo();
}
public class TurnOffLightCommand implements Command {
    private Light light;

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

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

    @Override
    public void undo() {
        light.turnOn(); // Light를 켜는 것으로 간주하여 Undo
    }
}

public class TurnOnLightCommand implements Command {
    private Light light;

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

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

    @Override
    public void undo() {
        light.turnOff(); // Light를 끄는 것으로 간주하여 Undo
    }
}

 

Command에 작업을 취소할 때의 Receiver의 행위에 대해서 작성한 메서드를 추가해준다.

 

 

// 리모컨(Invoker)
public class RemoteControl {
    private List<Command> commands = new ArrayList<>();

    public void setCommand(Command command) {
        this.commands.add(command);
    }

    public void pressAllButton() {
        for(Command command: commands){
            command.execute();
        }
    }

    // Undo 기능 수행
    public void undoLastCommand() {
        if (!commands.isEmpty()) {
            Command lastCommand = commands.get(commands.size() - 1);
            lastCommand.undo();
            commands.remove(commands.size() - 1);
        }
    }
}

 

Invoker에는 마지막 명령을 취소할 수 있는 기능을 지원하도록 메서드를 추가해주었다. 취소 시 Command의 undo 명령이 실행된다.

 

 

 

public class Client {
    public static void main(String[] args) {
        Light light = new Light();
        TurnOnLightCommand turnOnCommand = new TurnOnLightCommand(light);
        TurnOffLightCommand turnOffCommand = new TurnOffLightCommand(light);

        RemoteControl remoteControl = new RemoteControl();

        remoteControl.setCommand(turnOnCommand);
        remoteControl.setCommand(turnOffCommand);
        remoteControl.pressAllButton();
        remoteControl.undoLastCommand();
    }
}

 

Client에서는 마지막 동작을 취소하고 싶으면 Invoker의 undoLastCommand()를 호출하게 되고, 이 때 마지막 Command에 해당하는 undo()명령이 실행될 것이고 실제 Reveiver은 취소하는 행동을 실행한다.

반응형

BELATED ARTICLES

more