버튼을 눌렀을 때 램프의 불이 켜지는 프로그램을 개발한다고 생각하자. 램프를 켜려면 Button 클래스는 Lamp 객체를 참조해야 한다.
public class Lamp{
public void turnOn()}
System.out.println("Lamp ON");
}
}
public class Button{
private Lamp theLamp;
public Button(Lamp theLamp){
this.theLamp=theLamp;
}
public void pressed(){
theLamp.turnOn();
}
}
public class Client{
public static void main(String[] args){
Lamp lamp=new Lamp();
Button lampButton=new Button(lamp);
lampButton.pressed();
}
}
램프를 켜는 대신 알람을 시작하게 하려면 Button 클래스의 pressed 메서드를 수정해야 한다. 알람이 동작하도록 아래와 같이 수정했다. 하지만 기능을 변경하려고 기존 Button 클래스의 코드를 수정하는 것은 OCP에 위배된다.
public void pressed(){
theAlarm.start();
}
버튼을 누르듣 동작에 따라 다른 기능을 실행하게 하려면 기능이 실행되는 시점에 필요한 프로그램 또는 메서드를 선택할 수 있어야 한다. Button 클래스는 2가지 기능(램프켜기, 알람동작)을 모두 구현할 수 있어야 한다.
public class Button{
...
public void setMode(Mode mode){
this.theMode=mode;
}
public void pressed(){
switch(theMode){
case LAMP:
theLamp.turnOn();
break;
case ALARM:
theALARM.start();
break;
}
}
...
}
이 경우 역시 버튼을 눌렀을 때 기능을 변경하기 위해 다시 Button 클래스의 코드를 수정했다. Button 클래스에 새로 기능을 추가할 때마다 코드를 수정해야 한다면 Button 클래스는 재사용하기 어렵다.
Button 클래스의 pressed 메서드에서 구체적인 기능을 직접 구현하는 대신 버튼을 눌렀을 때 실행될 기능을 Button 클래스 외부에서 제공받아 캡슐화해 pressed 메서드에서 호출하는 방법을 사용하면 된다. 예를 들어 램프를 켜는 경우는 theLamp.turnOn 메서드를 호출하고 알람이 동작하는 경우에는 theAlarm.start 메서드를 호출하도록 pressed 메서드를 수정해야 한다.
Button 클래스는 램프 켜기 또는 알람 동작 등의 기능을 실행할 때 메서드를 직접 호출하지 않는다. 대신 Command 인터페이스의 execute 메서드를 호출한다. 그리고 LampOnCommand 클래스는 execute 메서드에서 Lamp 클래스의 turnON 메서들르 호출해 램프 켜는 기능을 구현한다. AlarmStartCommand 클래스에서는 Command 인터페이스의 execute 메서드를 구현해 Alarm 클래스의 start 메서드를 호출한다.
public interface Command{
public abstract void execute();
}
public class Button{
private Command theCommand;
...
public void pressed(){
theCommand.execute();//버튼이 눌리면 execute 실행
}
}
public class Lamp{
public void turnOn(){
System.out.println("Lamp On");
}
}
public class LampOnCommand implements Command{
private Lamp theLamp;
...
public void execute(){
theLamp.turnOn();
}
}
버튼을 눌렀을 때 필요한 임의의 기능은 Command 인터페이스를 구현한 클래스의 객체를 Button 객체에 설정해서 실행할 수 있다. 따라서 Button 클래스는 소스 코드를 변경하지 않으면서도 다양한 동작을 구현할 수 있다.
커맨드 패턴은 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고 재사용하고자 할 때 유용하다.
예를 들어 FileOpen과 FileClose 항목이 있을 때 MeneItem 클래스에 직접 구체적인 기능을 구현한다면, 재사용을 위해 FileOpen을 위한 MenuItem과 FileClose를 위한 MenuItem을 각각 구현해야 한다.
이런 경우에 커맨트 패턴을 이용하면 MenuItem 클래스를 재사용할 수 있다. FileOpen 메뉴의 기능과 FileClose 메뉴의 기능을 담당하는 Command 인터페이스를 구현한다. 그리고 MenuItem 클래스가 Command 인터페이스를 사용하도록 설계하면 MenuItem은 FileOpen과 FileClose를 메뉴 항목에서 그대로 재사용할 수 있다.
커맨드 패턴은 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자(Invoker) 클래스와 실제 기능을 실행하는 수신자(Receiver) 클래스 사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 호출자 클래스를 수정없이 그대로 사용할 수 있다.
실행될 기능에 대한 인터페이스. 실행될 기능을 execute 메서드로 선언함
실제로 실행되는 기능을 구현. 즉 Command 인터페이스 구현
기능의 실행을 요청하는 호출자 클래스
ConcreateCommand에서 execute 메서드를 구현할 때 필요한 클래스. 즉 ConcreateCommand 기능을 실행하기 위해 사용하는 수신자 클래스