6장 : 커맨드 패턴

커맨드 패턴

커맨드 패턴을 쓰면 어떤 작업을 요청한 쪽하고 그 작업을 처리한 쪽을 분리시킬 수 있다.

  • 커맨드 패턴을 이용하면 요구 사항을 객체로 캡슐화 할 수 있음

  • 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있음

  • 요청 내역을 큐에 저장하거나 로그로 기록할 수 있으며, 작업 취소 기능도 지원함

언제 사용 ?

  1. 작업들로 객체를 매개변수화하려는 경우

  2. 작업들의 실행을 예약하거나, 작업들을 대기열에 넣거나 작업들을 원격으로 실행하려는 경우

  3. 되돌릴 수 있는 작업을 구현하려고 하는 경우

구현 방법

1️⃣ 단일 실행 메서드로 커맨드 인터페이스 선언

public interface Command {
	public void execute();
}

2️⃣ 요청들을 커맨드 인터페이스를 구현하는 구상 커맨드 클래스들로 추출

  • 각 클래스에는 실제 수신자 객체에 대한 참조와 함께 요청 인수들을 저장하기 위한 필드들의 집합이 있어야 함.

  • 이러한 모든 값은 커맨드의 생성자를 통해 초기화되어야 함

public class LightOnCommand implements Command {
	Light light;

	public LightOnCommand(Light light) {
		this.light = light;
	}

	public void execute() {
		light.on();
	}
}

3️⃣ 발송자 역할을 할 클래스 식별하고, 해당 클래스들에 커맨드들을 저장하기 위한 필드들을 추가

  • 발송자들은 커맨드 인터페이스를 통해서만 커맨드들과 통신해야 함

  • 발송자들은 일반적으로 자체적으로 커맨드 객체들을 생성하지 않고 클라이언트 코드에서 가져옴

4️⃣ 수신자에게 직접 요청을 보내는 대신 커맨드를 실행하도록 발송자들을 변경

3 ~ 4

//
// This is the invoker
//
public class RemoteControl {
	Command slot;
 
	public RemoteControl() {}
  
	public void setCommand(Command command) {
		slot = command;
	}
 
	public void buttonWasPressed() {
		slot.execute();
	}
}

5️⃣ 클라이언트는 다음 순서로 객체들을 초기화 해야함

  • 수신자들을 만든다.

  • 커맨드들을 만들고 필요한 경우 수신자들과 연관시킨다.

  • 발송자들을 만들고 특정 커맨드들과 연관시킨다.

public class RemoteControlTest {
	public static void main(String[] args) {
		Light light = new Light(); // 1
		LightOnCommand lightOn = new LightOnCommand(light); // 2
		RemoteControl remote = new RemoteControl(); // 3

		remote.setCommand(lightOn); // 3
		remote.buttonWasPressed(); // 4
	}
}

구현 심화

  • 여러 요구 사항 전달

    • on/off

    • Light Off, Stereo On, GarageDoorOpen ..

  • 작동 취소 (Undo) 추가

1️⃣ 단일 실행 메서드로 커맨드 인터페이스 선언

undo() 메소드 추가

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

2️⃣ 요청들을 커맨드 인터페이스를 구현하는 구상 커맨드 클래스들로 추출

undo() 메소드 구현

public class LightOnCommand implements Command {
  Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.on();
  }
	
	public void undo() {
		light.off()
	}
}

기존의 Light 에서 OffCommand 추가

// 추가 
public class LightOffCommand implements Command {
	Light light;

	public LightOnCommand(Light light) {
		this.light = light;
	}

	public void execute() {
		light.off();
	}

	public void undo() {
		light.on()
	}
}

스피커 요구사항 추가

// 스피커 On
public class StereoOnWithCDCommand implements Command {
	Stereo stereo;
 
	public StereoOnWithCDCommand(Stereo stereo) {
		this.stereo = stereo;
	}
 
	public void execute() {
		stereo.on();
		stereo.setCD();
		stereo.setVolume(11);
	}

	public void undo() {
		stereo.off();
	}
}
// 스피커 Off
public class StereoOffCommand implements Command {
	Stereo stereo;
 
	public StereoOffCommand(Stereo stereo) {
		this.stereo = stereo;
	}
 
	public void execute() {
		stereo.off();
	}

	public void undo() {
		stereo.on();
		stereo.setCD();
		stereo.setVolume(11);
	}
}

3️⃣ 발송자 역할을 할 클래스 식별하고, 해당 클래스들에 커맨드들을 저장하기 위한 필드들을 추가

4️⃣ 수신자에게 직접 요청을 보내는 대신 커맨드를 실행하도록 발송자들을 변경

3 ~ 4

  • 여러 요구 사항 매개변수로 받을 수 있도록 변경

  • undoCommand 추가

//
// This is the invoker
//
public class RemoteControl {
	Command[] onCommands;
	Command[] offCommands;
	Command undoCommand;
 
	public RemoteControl() {
		onCommands = new Command[7];
		offCommands = new Command[7];
 
		Command noCommand = new NoCommand();
		for (int i = 0; i < 7; i++) {
			onCommands[i] = noCommand;
			offCommands[i] = noCommand;
		}
		undoCommand = noCommand;
	}
  
	public void setCommand(int slot, Command onCommand, Command offCommand) {
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
 
	public void onButtonWasPushed(int slot) {
		onCommands[slot].execute();
		undoCommand = onCommand[slot];
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
		undoCommand = onCommand[slot];
	}

	public void undoButtonWasPushed() {
		undoCommand.undo();
	}
}
public class NoCommand implements Command {
	public void execute() { }
	public void undo() { }
}

5️⃣ 클라이언트는 다음 순서로 객체들을 초기화 해야함

  • 수신자들을 만든다.

  • 커맨드들을 만들고 필요한 경우 수신자들과 연관시킨다.

  • 발송자들을 만들고 특정 커맨드들과 연관시킨다.

public class RemoteControlTest {
 
	public static void main(String[] args) {
		RemoteControl remoteControl = new RemoteControl();
 
		Light livingRoomLight = new Light("Living Room");
		Light kitchenLight = new Light("Kitchen");
		Stereo stereo = new Stereo("Living Room");
  
		LightOnCommand livingRoomLightOn = 
				new LightOnCommand(livingRoomLight);
		LightOffCommand livingRoomLightOff = 
				new LightOffCommand(livingRoomLight);

 
		StereoOnWithCDCommand stereoOnWithCD =
				new StereoOnWithCDCommand(stereo);
		StereoOffCommand  stereoOff =
				new StereoOffCommand(stereo);
 
		remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
		remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
		remoteControl.setCommand(3, stereoOnWithCD, stereoOff);
  
		System.out.println(remoteControl);
 
		remoteControl.onButtonWasPushed(0);
		remoteControl.offButtonWasPushed(0);
		remoteControl.onButtonWasPushed(1);
		remoteControl.offButtonWasPushed(1);
		remoteControl.onButtonWasPushed(3);
		remoteControl.offButtonWasPushed(3);
	}
}
  • 로그 추가(Stack)

design-patterns/src/command/example at main · SoobinJung1013/design-patterns

장단점

장점

  • 단일 책임 원칙

    • 작업을 호출하는 클래스들을 작업을 수행하는 클래스들로부터 분리할 수 있음

  • 개방/폐쇄 원칙

    • 기존 클라이언트 코드를 손상하지 않고 앱에 새커맨드들을 도입할 수 있음

  • 실행 취소/다시 실행을 구현할 수 있음

  • 작업들의 지연된 실행을 구현할 수 있음

  • 간단한 커맨드들의 집합을 복잡한 커맨드로 조합할 수 있음

단점

  • 발송자와 수신자 사이에 완전히 새로운 레이어를 도입하기 떄문에 코드가 더 복잡해질 수 있음

Refactoring Guru Code

발표 자료

Last updated