명령(command) 패턴

의도

요청 자체를 캡슐화하는 것으로, 요청이 서로 다른 사용자를 매개변수로 만들고 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원한다.

활용성

장점
  • 수행할 동작을 객체로 매개변수화하고자 할 때
    • 절차 지향 프로그램에서는 이를 콜백(callback) 함수, 즉 어딘가 등록되었다가 나중에 호출되는 함수를 사용해서 이러한 매개변수화를 표헌한다.
      객체 지향 방식으로 이를 나타낸 것이 바로 명령 패턴이다.
  • 서로 다른 시간에 요청을 명시하고, 저장하며, 실행하고 싶을 때
    • Command 객체는 원래의 요청과 다른 생명주기(life time)가 있다. 요청을 받아 처리하는 객체가 주소 지정 방식과는 독립적으로 표현될 수 있다면,
      Command 객체를 다른 프로세스에게 넘겨주고 거기서 해당 처리를 진행하게 할 수 있다.

구조

alt

  • Command
    • 연산 수행에 필요한 인터페이스를 선언한다.
  • ConcreteCommand
    • Receiver 객체와 액션 간의 연결성을 정의한다.
      또한 처리 객체에 정의된 연산을 호출하도록 Execute를 구현한다.
  • Client
    • 객체를 생성하고 처리 객체로 정의한다.
  • Invoker
    • Command에 처리를 수행할 것을 요청한다.
  • Receiver
    • 요청에 관련된 연산 수행 방법을 알고 있다.
      어떤 클래스도 요청 수신자로서 동작할 수 있다.

협력 방법

  • Client는 ConcreteCommand 객체를 생성하고 이를 수신자로 지정한다.
  • Invoker 클래스는 ConcreteCommand 객체를 저장한다.
  • Invoker 클래스는 command에 정의된 Execute() 호출하여 요청을 발생시킨다.
    명령어가 취소 가능한 것이라면 ConcreteCommand는 이전에 Execute() 호출 전 상태의 취소 처리를 위해 저장한다.
  • ConcreteCommand 객체는 요청을 실제 처리할 객체에 정의된 연산을 호출한다.

결과

  • Command는 연산을 호출하는 객체와 연산 수행 방법을 구현하는 객체를 분리한다.
  • Command는 일급 클래스이다. 다른 객체와 같은 방식으로 조작되고 확장할 수 있다.
  • 새로운 Command 객체를 추가하기 쉽다. 기존 클래스르 변경할 필요없이 단지 새로운 명렁어에 대응하는 클래스만 정의하면 된다.

예제 코드

// Invoker class
public class Switch {
	private Command flipUpCommand;
	private Command flipDownCommand;
	
	public Switch(Command flipUpCommand, Command flipDownCommand) {
		this.flipUpCommand = flipUpCommand;
		this.flipDownCommand = flipDownCommand;
	}
	
	public void flipUp() {
		flipUpCommand.execute();
	}
	
	public void flipDown() {
		flipDownCommand.execute();
	}
}

// Receiver class
public class Light {
	public Light() {}
	
	public void turnOn() {
		System.out.println("The light is on");
	}
	
	public void turnOff() {
		System.out.println("The light is off");
	}
}

// Command interface
public interface Command {
	void execute();
}

// ConcreteCommand 1
public class TurnOnLightCommand implements Command {
	private Light light;
	
	public TurnOnLightCommand(Light light) {
		this.light = light;
	}
	
	public void execute() {
		this.theLight.turnOn();
	}
}

// ConcreteCommand 2
public class TurnOffLightCommand implements Command {
	private Light light;
	
	public TurnOffLightCommand(Light light) {
		this.light = light;
	}
	
	public void execute() {
		light.turnOff();
	}
}

// Client
public class TestCommand {
	public static void main(String[] args){
	  Light light = new Light();
	  Command switchUp = new TurnOnLightCommand(light);
	  Command switchDown = new TurnOffLightCommand(light);
	  
	  Switch sw = new Switch(switchUp, switchDown);
	  sw.flipUp();
	  sw.flipDown();
	}
}