목차

Command pattern

🗓️

Command 패턴

  • Command패턴은 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고도 재사용을 가능하게 할때 유용하다.
  • 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 Invoker 클래스와 실제 기능을 실행하는 Receiver클래스 사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 Invoker 클래스를 수정없이 그대로 사용할 수 있도록 해준다.

만능버튼 만들기

  • 램프를 켜는 버튼을 만들어보자
public class Lamp {

    public void turnOn() {
        System.out.println("Lamp On!");
    }
}
public class Button {

    private Lamp lamp;

    public Button(Lamp lamp) {
        this.lamp = lamp;
    }

    public void pressed() {
        lamp.turnOn();
    }

}
public class Client {

    public static void main(String[] args) {
        Lamp lamp = new Lamp();
        Button lampButton = new Button(lamp);
        lampButton.pressed();
    }
}
  • Button클래스의 생성자를 이용해 불을 켤 Lamp객체를 전달한다.
  • Button클래스의 pressed메소드가 호출되면 생성자를 통해 전달받은 Lamp 객체의 turnOn메소드를 호출해 불을 켠다.

문제점

  • 램프가 켜지는 기능 대신 다른 기능을 실행하려면 어떤 변경 작업을 해야 하는가?
  • 버튼을 누르는 동작에 따라 기능을 실행하게 하려면 어떤 변경 작업을 해야 하는가?

1. 다른 기능의 실행

  • 램프 대신 알람을 시작하게 하려면 Button클래스를 수정해야한다.
public class Alarm {

    public void start() {
        System.out.println("Rrrrrrr..!");
    }

}
public class Button {

    private Alarm alarm;

    public Button(Alarm alarm) {
        this.alarm = alarm;
    }

    public void pressed() {
        alarm.start();
    }

}
  • 기능을 변경하려고 Button클래스의 코드를 변경하는것은 OCP에 위배된다. 버튼을 눌렀을때 지정된 특정기능만 고정적으로 수행하도록 만든 처음 디자인은 다른 기능을 추가할때 위 처럼 메소드 전체를 변경해야하므로 OCP를 위배하는 것이다.

2. 누르는 동작에 따라 다른 기능을 실행하는 경우

  • 버튼을 누르는 동작에 따라 다른 기능을 실행하게 하려면 기능이 실행되는 시점에 필요한 프로그램 또는 메소드를 선택할 수 있어야한다.
  • 램프와 알람기능 모두 구현한 코드다.
public class Button {

    private Lamp lamp;
    private Alarm alarm;
    private Mode mode;

    enum Mode {LAMP, ALARM};

    public Button(Lamp lamp, Alarm alarm) {
        this.lamp = lamp;
        this.alarm = alarm;
    }

    public void setMode(Mode mode) {
        this.mode = mode;
    }

    public void pressed() {
        switch (mode) {
            case LAMP:
                lamp.turnOn();
                break;
            case ALARM:
                alarm.start();
                break;
        }
    }

}
public class Client {

    public static void main(String[] args) {
        Lamp lamp = new Lamp();
        Alarm alarm = new Alarm();
        Button button = new Button(lamp, alarm);

        button.setMode(Mode.LAMP);
        button.pressed();

        button.setMode(Mode.ALARM);
        button.pressed();
    }
}
  • 이 역시 새로운 기능을 추가할 때 마다 코드를 수정해야해 재사용성이 떨어진다.

해결책

  • 새로운 기능을 추가하거나 변경하더라도 Button클래스를 그대로 사용하려면 Button클래스의 pressed 메소드에서 구체적인 기능을 직접 구현하는 대신 버튼을 눌렀을때 실행될 기능을 Button클래스 외부에서 제공받아 캡슐화해 pressed메소드에서 호출하는 방법을 사용할 수 있다.
  • 다음은 설계다.
  • Button클래스는 램프 기능을 실행할 때 Lamp클래스의 turnOn메소드나 Alarm클래스의 start메소드를 직접 호출하지 않는다. 대신 미리 약속된 Command인터페이스의 execute메소드를 호출한다.
  • 그리고 LampOnCommand클래스에서는 execute메소드를 구현해 램프 켜는 기능을 구현한다. AlarmStartCommand역시 마찬가지다.
public interface Command {

    public abstract void execute();

}

public class Button {

    private Command command;

    public Button(Command command) {
        setCommand(command);
    }

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

    public void pressed() {
        command.execute();
    }
}
public class Lamp {

    public void trunOn() {
        System.out.println("Lamp On.");
    }
}
public class LampOnCommand implements Command {

    private Lamp lamp;

    public LampOnCommand(Lamp lamp) {
        this.lamp = lamp;
    }


    @Override
    public void execute() {
        lamp.trunOn();
    }
}
public class Alarm {

    public void start() {
        System.out.println("Rrrrrr..!");
    }

}
public class AlarmOnCommand implements Command {

    private Alarm alarm;

    public AlarmOnCommand(Alarm alarm) {
        this.alarm = alarm;
    }

    @Override
    public void execute() {
        alarm.start();
    }
}
public class Client {

    public static void main(String[] args) {
        Lamp lamp = new Lamp();
        Command lampOnCommand = new LampOnCommand(lamp);

        Button button1 = new Button(lampOnCommand);
        button1.pressed();

        Alarm alarm = new Alarm();
        Command alarmOnCommand = new AlarmOnCommand(alarm);

        Button button2 = new Button(alarmOnCommand);
        button2.pressed();

        button2.setCommand(lampOnCommand);
        button2.pressed();
    }

}
  • Command 인터페이스를 구현하는 LampOnCommand와 AlarmOnCommand 객체를 Button객체에 설정한다.
  • Button클래스의 pressed메소드에서 Command인터페이스의 execute 메소드를 호출할 수 있게 함으로써 LampOnCommand와 AlarmOnCommand 클래스의 execute메소드를 실행할 수 있다.
  • 버튼을 눌렀을때 필요한 기능은 Command인터페이스를 구현한 클래스의 객체를 Button객체에 설정해서 실행할 수 있다.
  • 여기서 LampOffCommand를 추가해보자.
public class Lamp {

    public void trunOn() {
        System.out.println("Lamp On.");
    }

    public void turnOff() { ///<<<<<<<<<<<<<<<
        System.out.println("Lamp Off.");
    }
}
public class LampOffCommand implements Command {

    private Lamp lamp;

    public LampOffCommand(Lamp lamp) {
        this.lamp = lamp;
    }


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

}
public class Client {

    public static void main(String[] args) {
        Lamp lamp = new Lamp();
        Command lampOffCommand = new LampOffCommand(lamp);
        button1.setCommand(lampOffCommand);
        button1.pressed();
    }

}