본문 바로가기

Design pattern

[JAVA 디자인패턴] Command pattern(커맨드 패턴)

 

커맨드 패턴을 이해하기 위해, 만능 버튼을 만들어보자. 만능 버튼은 눌리면 어떠한 기능도 수행할 수 있다.

1
2
3
4
5
public class Lamp{
    public void turnOn(){
        System.out.println("Lamp on");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5; text-decoration:none">Colored by Color Scripter

 

1
2
3
4
5
6
7
8
9
10
public class Button{
    private Lamp lamp;
    
    public void setLamp(Lamp lamp){
        this.lamp = lamp;
    }
    public void pressed(){
        lamp.trunOn();
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5; text-decoration:none">Colored by Color Scripter

버튼 클래스에 setter를 이용해서 Lamp를 설정해주었다. 그리고 버튼을 누르면, lamp가 켜지도록 하였다.

이와같은 설계방식은 어떠한가? 적절해보이는가? 이를 판단하기 위해서 무엇이 변화하는 지 생각해보고 문제점을 찾아보자.

 

lamp를 키는게 아니라, 알람이나 TV를 시작하게 하려면?

 

위와같은 설계에서, 알람을 시작하도록 하려면 pressed를 수정해야 한다. 또한, 멤버변수로 추가도 해줘야 한다. 결국, OCP를 만족하지 않는다.

 

일반화된 개념을 한번 생각해보자.(가능한 바뀌지 않는 개념과 연관관계를 맺기위해) lamp, TV, Alarm의 일반화된 개념이 무엇인가? 찾기 어렵다. Device라는 일반화된 개념이라고 하자. 그러면 일반화의 의미가 없어진다. 어떻게든 lamp와 TV, Alarm을 일반화해서 기능을 구현했다고 하자. 하지만, 자동차가 추가된다면 동일한 기능을 찾아보기 매우 어렵다.

결국, LSP도 만족시키기 어려워 진다. 왜냐하면 기능을 확장을 할 수가 없다. 서로의 행위가 매우 다르기 때문이다. 결론적으로, 대상에 대해서 일반화된 개념을 찾기 어렵다

하지만 우리는 일반화를 해야한다. 그렇지 않으면 위의 코드처럼 버튼을 누를 때 다른 기능을 수행하려면 코드를 바꿔야 하게 된다. OCP를 만족시키기 않게 된다. 하지만, 대상에 대해서는 일반화할 수 없다.

그렇다면, 대상이 아니라 행위에 대해서 일반화하면 된다. strategy pattern이 대상에 대한 일반화라면, command 패턴은 행위에 대한 일반화를 알려주는 패턴이다. 결국 구조는 strategy 패턴이랑 크게 다를게 없다.

행위에 대한 큰 개념을 만들고 그 개념을 재구현하는 클래스를 만들면 된다. 재구현하는 클래스는 Lamp, Car, Alarm 클래스들을 가리킨다.

 

그렇다면 위의 구조가 OCP를 만족하는 지 생각해보자.

만약 만능 버튼에 문을 여는 기능을 추가한다고 하자. 그렇다면, 어떻게 하면 될까?

 

 

Command 인터페이스를 재구현하는 클래스를 만든다. 그리고 그 클래스는 행위의 대상을 의존하고 있으면 된다. 다른 클래스의 코드는 바뀌지 않는다. Button에서 Command 멤버 변수만 setter로 바꿔주면 된다. strategy pattern과 매우 유사한 구조이다.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class Button{
    private Command command;
    
    public void setCommand(Command command){
        this.command = command;
    }
    public void pressed(){
        command.execute();
    }
}
 
public interface Command{
    void execute();
}
 
public class LampPowerCommand implements Command{
    private Lamp lamp;
    
    public void setLamp(Lamp lamp){
        this.lamp = lamp;
    }
    
    public void execute(){
        lamp.lampOn();
    }
}
 
public class Lamp{
    
    public void lampOn(){
        System.out.println("Lamp on");
    }
}
 
public class CarEngineOnOffCommand(){
    private Car car;
    
    public void setCar(Car car){
        this.car = car;
    }
    public void execute(){
        car.powerOn();
    }
}
 
public class Car{
    System.out.println("Car power on");
}
 
public class Main{
    public static void main(String[] args){
        Button button = new Button();
        Lamp lamp = new Lamp();
        LampPowerCommand lampPowerCommand = new LampPoewrCommand;
        lampPowerCommand.setLamp(lamp);
        button.setCommand(lapmPowerCommand);
        
        button.pressed(); // Lamp on.
        
        Car car = new Car();
        CarEngineOnOffCommand carEngineOnOffCommand = new CarEngineOnOffCommand();
        button.setCommand(carEngineOnOffCommand);
        
        button.pressed(); // Car power on.
    }
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5; text-decoration:none">Colored by Color Scripter

 

다른 기능으로 버튼을 바꾸어도 전혀 클래스에 영향을 주지 않는다. 기능을 추가해도 문제가 발생하지 않는다.

Command 패턴은 이벤트가 자주 변경될 때 유익하다. 위의 예제와 비슷하게 단순히 버튼만 누르는데, 이벤트가 바뀌는 환경에서 쓰면 된다.

 

참고문헌

[JAVA 객체지향 디자인 패턴, 정인상]