본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

Command

by 민휘 2023. 2. 24.

핵심 의도

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화하거나 메모리에 보관할 수 있다.

 

적용 상황

요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다. 재실행이나 우선순위에 따라 명령어를 실행하거나 배치 실행도 가능하다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

요청을 보내는 책임과 그 요청을 처리하는 책임은 각각 Client와 Receiver에게 있다. 예를 들어 사용자가 선풍기를 켜고 싶다면 요청을 보내는 책임은 사용자에게, 선풍기를 켜는 요청을 처리하는 책임은 선풍기에게 있다. 그리고 사용자의 요청들을 중간에서 기록하고 전달하는 책임은 Invoker가 한다. 사용자가 선풍기 뿐만 아니라 부엌 불, 라디오 등을 키거나 끄고 싶다면 리모컨에 명령을 기록해두고 요청을 날릴 수 있는데 리모컨이 Invoker의 역할을 한다. 그리고 Invoker가 사용자의 요청을 Reciever에게 보내려면 통일된 인터페이스로 묶어주어야 하는데 이것이 Command 인터페이스이고, 이를 구현한 구체적인 커맨드 클래스는 Reciever에게 요청을 보내는 역할을 한다.

 

구현 포인트

  1. ConcreteCommand는 execute()에서 Receiver 객체에게 메시지를 요청해 동작 수행을 요청한다.
  2. ConcreteCommand가 undo() 동작을 수행할 때는 이전에 요청 내용과 상태를 저장해두었다가 반대 동작을 수행한다. 예를 들어 불을 켜는 동작에 대해 취소를 요청했다면 불을 끄는 동작을 실행하면 된다.
  3. Invoker의 생성자로 커맨드 클래스를 초기화할 때 빈 클래스는 NoCommand 객체로 초기화한다. NoCommand는 null 객체인데, 딱히 리턴할 객체가 없고 클라이언트가 null을 처리하지 않게 하고 싶을 때 활용한다.
  4. Invoker의 Command 클래스를 초기화할 때, ConcreteCommand 클래스 생성자를 하나씩 생성해 넘겨주기보다 람다식을 사용하면 더 깔끔하게 코드를 작성할 수 있다. 하지만 Command 인터페이스에 메소드가 하나일 때만 가능하다.

 

적용 예시

요구사항

홈 오토메이션 장치 관리를 위한 리모컨을 개발한다. 리모컨에는 7개의 슬롯이 있고, 슬롯마다 on off 버튼이 있다. 각 버튼에 명령을 할당하면 그 버튼에 맞는 커맨드 객체의 execute() 메소드가 호출되고, 조명 선풍기 오디오 등에서 특정 행동을 담당하는 메소드가 실행된다.

 

설계

Client는 RemoteLoader(테스트 클래스)이, Invoker는 RemoteControl, Receiver는 Light 등의 클래스가 담당한다. ConcreteCommand는 LightOnCommand, LightOffCommand 등의 클래스가 구현하고 Receiver의 메소드를 호출한다.

 

코드

Command

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

LightOnCommand(ConcreteCommand)

public class LightOnCommand implements Command {
	Light light;
	int level;
	public LightOnCommand(Light light) {
		this.light = light;
	}
 
	public void execute() {
        level = light.getLevel();
		light.on();
	}
 
	public void undo() {
		light.dim(level);
	}
}

Light(Reciever)

public class Light {
	String location;
	int level;

	public Light(String location) {
		this.location = location;
	}

	public void on() {
		level = 100;
		System.out.println("Light is on");
	}

	public void off() {
		level = 0;
		System.out.println("Light is off");
	}

	public void dim(int level) {
		this.level = level;
		if (level == 0) {
			off();
		}
		else {
			System.out.println("Light is dimmed to " + level + "%");
		}
	}

	public int getLevel() {
		return level;
	}
}

RemoteControlWithUndo(Invoker)

public class RemoteControlWithUndo {
	Command[] onCommands;
	Command[] offCommands;
	Command undoCommand;
 
	public RemoteControlWithUndo() {
		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 = onCommands[slot];
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
		undoCommand = offCommands[slot];
	}
 
	public void undoButtonWasPushed() {
		undoCommand.undo();
	}
  
	public String toString() {
		StringBuffer stringBuff = new StringBuffer();
		stringBuff.append("\\n------ Remote Control -------\\n");
		for (int i = 0; i < onCommands.length; i++) {
			stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
				+ "    " + offCommands[i].getClass().getName() + "\\n");
		}
		stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\\n");
		return stringBuff.toString();
	}
}

RemoteLoader

public class RemoteLoader {
 
	public static void main(String[] args) {
		RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
 
		Light livingRoomLight = new Light("Living Room");
 
		LightOnCommand livingRoomLightOn = 
				new LightOnCommand(livingRoomLight);
		LightOffCommand livingRoomLightOff = 
				new LightOffCommand(livingRoomLight);
 
		remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
 
		remoteControl.onButtonWasPushed(0);
		remoteControl.offButtonWasPushed(0);
		System.out.println(remoteControl);
		remoteControl.undoButtonWasPushed();
		remoteControl.offButtonWasPushed(0);
		remoteControl.onButtonWasPushed(0);
		System.out.println(remoteControl);
		remoteControl.undoButtonWasPushed();

		CeilingFan ceilingFan = new CeilingFan("Living Room");
   
		CeilingFanMediumCommand ceilingFanMedium = 
				new CeilingFanMediumCommand(ceilingFan);
		CeilingFanHighCommand ceilingFanHigh = 
				new CeilingFanHighCommand(ceilingFan);
		CeilingFanOffCommand ceilingFanOff = 
				new CeilingFanOffCommand(ceilingFan);
  
		remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
		remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
   
		remoteControl.onButtonWasPushed(0);
		remoteControl.offButtonWasPushed(0);
		System.out.println(remoteControl);
		remoteControl.undoButtonWasPushed();
  
		remoteControl.onButtonWasPushed(1);
		System.out.println(remoteControl);
		remoteControl.undoButtonWasPushed();
	}

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

Facade  (0) 2023.02.24
Adapter  (0) 2023.02.24
Singleton  (0) 2023.02.24
Simple Factory, Factory Method, Abstract Factory  (0) 2023.02.24
Decorator  (0) 2023.02.24