실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴으로 하나의 추상 클래스에 메서드를 만들어 각 명령이 들어오면 그에 맞는 서브 클래스가 선택되는 특징을 갖는 패턴이다.
커맨드 패턴을 사용하면 어떤 객체(A)에서 다른 객체(B)의 메서드를 실행하려고 할 때 의존성을 제거할 수 있다. 또한 기능이 수정되거나 변경이 일어날 때 A클래스의 코드를 수정없이 기능에 대한 클래스를 정의하면 되므로 시스템이 확장성이 있으면서 유연성을 가질 수 있다.
- Command: 실행될 기능에 대한 인터페이스
실행될 기능을 execute 메서드로 선언- ConcreteCommand: 실제로 실행되는 기능을 구현
Command 인터페이스를 구현- Invoker: 기능의 실행을 요청하는 호출자 클래스
Command 객체를 캡슐화하며 요청을 처리하기 위해 커맨드 객체에 요청을 전달한다.- Receiver: ConcreteCommand에서 execute 메서드를 구현할 때 필요한 클래스
ConcreteCommand의 기능을 실행하기 위해 사용하는 수신자 클래스
아래 예제에서 AISpeaker는 IoT 기능이 탑재된 스마트홈 제품이다.
// AISpeaker.java
public class AISpeaker {
private Light light;
public AISpeaker(Light light) {
this.light = light;
}
public void talk() {
light.turnLightOn();
}
}
// Light.java
public class Light {
public void turnLightOn() {
System.out.println("[Light] turning light on...");
}
}
// Client.java
public class Client {
public static void main(String[] args) {
AISpeaker speaker = new AISpeaker(new Light());
speaker.talk();
}
}
// 실행 결과
[Light] turning light on...
AISpeaker는 Light를 속성으로 가지며 Light 객체의 turnLightOn() 메소드를 사용해 집의 전등을 켜는 동작을 한다. 만약 AISpeaker에 에어컨을 켜는 기능을 추가하려면 어떻게 해야 할까?
// AirConditioner.java
public class AirConditioner {
public void turnAirConditionerOn() {
System.out.println("[AirConditioner] turning air conditioner on...");
}
}
// AISpeaker.java
public class AISpeaker {
private Light light;
private AirConditioner airConditioner;
private String mode = "";
public AISpeaker(Light light, AirConditioner airConditioner) {
this.light = light;
this.airConditioner = airConditioner;
}
public void setMode(String mode) {
this.mode = mode;
}
public void talk() {
switch(mode) {
case "light" :
light.turnLightOn();
break;
case "airConditioner" :
airConditioner.turnAirConditionerOn();
break;
case "" :
System.out.println("[AISpeaker] Please set mode before operating...");
break;
default :
System.out.println("[AISpeaker] Please set mode properly...");
break;
}
}
}
// Client.java
public class Client {
public static void main(String[] args) {
AISpeaker speaker = new AISpeaker(new Light(), new AirConditioner());
speaker.talk();
speaker.setMode("test");
speaker.talk();
speaker.setMode("airConditioner");
speaker.talk();
}
}
// 실행 결과
[AISpeaker] Please set mode before operating...
[AISpeaker] Please set mode properly...
[AirConditioner] turning air conditioner on...
우선 AirConditioner 클래스를 생성하고, AISpeaker 클래스에 AirConditioner 속성을 추가해준 다음, 여러개의 동작 중 하나를 선택하기 위해 mode 속성을 추가해주고 talk() 메소드에 switch-case문을 사용해 모드에 맞는 동작을 하게끔 수정했다.
이와 같은 방식은 기능이 추가될 때마다 번거로운 수정 작업을 거쳐야 하고, 기능이 많아질 수록 talk메서드의 분기 또한 늘어날 것이다.
// Command.java (Command)
public interface Command {
public void run();
}
// AirConditionerOnCommand.java (ConcreteCommand)
public class AirConditionerOnCommand implements Command {
private AirConditioner airConditioner;
public AirConditionerOnCommand(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void run() {
airConditioner.turnAirConditionerOn();
}
}
// LightOnCommand.java (ConcreteCommand)
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void run() {
light.turnLightOn();
}
}
// AirConditioner.java (Receiver)
public class AirConditioner {
public void turnAirConditionerOn() {
System.out.println("[AirConditioner] turning air conditioner on...");
}
}
// Light.java (Receiver)
public class Light {
public void turnLightOn() {
System.out.println("[Light] turning light on...");
}
}
// AISpeaker.java (Invoker)
public class AISpeaker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void talk() {
command.run();
}
}
// Client.java (Client)
public class Client {
public static void main(String[] args) {
AISpeaker speaker = new AISpeaker();
speaker.setCommand(new LightOnCommand(new Light()));
speaker.talk();
speaker.setCommand(new AirConditionerOnCommand(new AirConditioner()));
speaker.talk();
}
}
// 실행 결과
[Light] turning light on...
[AirConditioner] turning air conditioner on...
이렇게 구현할 경우 기능이 추가되면 추가된 기능을 Command를 상속하는 클래스로 만들고 장치가 늘어날 경우 장치 클래스를 만들고 해당 장치의 기능을 실행하는 Command 클래스를 만드는 식으로 기존 코드의 수정없이 확장을 할수 있다. (객체지향 설계 원칙 - OCP)
[디자인패턴] 디자인패턴이란? - 생성패턴, 구조패턴, 행위패턴