버튼을 누르면 불이 켜지는 램프가 있다. 버튼은 불이 켜지는 기능을 가진 것이다.
이를 프로그램으로 작성한다고 하면, 보통 아래와 같은 UML 다이어그램으로 설계하기 쉽다.
public class Button(){
private Lamp theLamp; //Lamp를 참조하기 위함
public Button(Lamp theLamp){ //생성자
this.theLamp = theLamp ;
}
public Button pressed(){ //Button을 누르는 경우 Lamp를 turnOn
theLamp.turnOn();
}
}
public class Lamp(){
public void turnOn(){
System.out.println("Lamp On");
}
}
public static void main(String [] args){
Lamp lamp = new Lamp();
Button b = new Button(lamp);
b.pressed();
}
이와 같이 설계하는 경우 어떠한 문제점이 있을까?
Button이 다른 추가 기능을 가지게 되는 경우를 생각해보자. Button에서 pressed() 함수에서 어떠한 방식으로 기능 선택할 것인지에 대해서 작성한 부분이 추가로 수정되어야할 것이다. 이는 OCP의 원리에 위배될 것이다.
이러한 경우에 커맨드 패턴을 사용한다. 커맨드 클래스를 이용한다면 기능들을 묶어 간편하게 관리하고 확장이 가능하다.
커맨드 패턴은 실행될 다양한 기능을 캡슐화한다. 따라서 확장이 용이하며, 클래스 사이의 의존성을 제거하여 기능의 변경에도 클래스를 사용할 수 있도록 해준다.
다음은 커맨드 패턴의 일반적인 구조이다.
Command 클래스는 기능에 대한 인터페이스이다. 이를 상속한 세부적인 클래스에 관한 것이 concreteCommand 클래스이다.
Invoker 클래스는 기능 실행을 invoke(요청) 하는 클래스이다.
Receiver는 요청을 받아 동작하는 클래스이다.
앞서 예시를 들었던 버튼을 커맨드 패턴으로 다시 구성해보자.
Button 클래스는 command를 호출하므로 invoker에 해당한다. pressed 되면 command를 호출해야하므로 theCommand를 이용하여 참조한다.
Command 클래스는 인터페이스이다. LampOnCommand, LampOffCommand가 Command 클래스의 자식 클래스로, Lamp를 참조하여 불을 키고 끄는 기능을 수행하게 한다.
Lamp 클래스는 기능을 수행받기 때문에 Reciever 클래스에 해당한다.