[6장] Command Pattern

ss0510s·2022년 7월 11일
0

Command Pattern

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다. 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능, 트랜잭션 시스템을 구현할 수 있다.

리모컨 코드

리모컨의 각 슬롯을 클릭했을 때, 명령을 통해 행동을 수행하고 각 슬롯마다 다른 명령을 수행해야 한다.

Command Interface

  • 모든 커맨드 객체에서 구현해야 하는 인터페이스이다.
  • execute() 메서드를 생성한다.
public interface Command {
	public void execute();
}

Command Concrete

  • 행동을 수행할 구상 클래스이다.
  • 리시버 객체를 생성한다.
  • excute 함수가 호출되면 리시버 객체에게 특정 작업을 처리하라는 지시를 전달한다.
public class LightOnCommand implements Command{
	Light light; 
    
    // 특정 조명 객체를 받아 인스턴스 변수에 저장
    public LightOnCommand(Light light) {
    	this.light = light;
    }
    
    public void execute() {
    	light.on(); // light 객체를 리시버로 지정
    }
}

public class LightOffCommand implements Command{
	Light light; 
    
    // 특정 조명 객체를 받아 인스턴스 변수에 저장
    public LightOffCommand(Light light) {
    	this.light = light;
    }
    
    public void execute() {
    	light.off(); // light 객체를 리시버로 지정
    }
}

Receiver Class

  • 어떤 행위를 처리해야 하는지 저장하여 요구 사항을 수행할 때 어떤 일을 처리해야 하는지 알고 있다.
public class Light {
	String location = "";

	public Light(String location) {
		this.location = location;
	}
	// 어떤 행위를 처리해야 하는지 알고있는 객체
	public void on() {
		System.out.println(location + " light is on");
	}

	public void off() {
		System.out.println(location + " light is off");
	}
}

Invoker Class

  • 커맨드 객체에게 어떤 행위를 해달라고 요청한다.
  • 명령어 저장 메서드를 정의하고, execute() 메소드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 한다.
public class RemoteControl {
	// on, off 명령어를 저장할 배열
	Command[] onCommands;
	Command[] offCommands;
 
	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;
		}
	}
  
  	// 명령어 저장 메서드 - slot 번호에 on,off 커맨드 객체 저장
	public void setCommand(int slot, Command onCommand, Command offCommand) {
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
 
 	// 행위 수행 메서드 - 커맨드 객체에게 행위 수행 요청
	public void onButtonWasPushed(int slot) {
		onCommands[slot].execute();
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
	}
  
	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");
		}
		return stringBuff.toString();
	}
}

Test.java

public class RemoteControlTest{
	public static void main(String[] args) {
    	RemoteControl remote = new RemoteControl(); // 인보커 객체 생성
        Light livingRoomlight = new Light("Living Room"); // 위치에 맞게 객체 생성 - 리시버 객체 생성
        
        // 커맨드 객체 생성
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); 
        
        remote.setCommand(0, livingRoomLightOn, livingRoomLightOff); // 커맨드 객체를 인보커에게 전달, 행위를 슬롯에 저장
        // remote.setCommand(0, ()->livingRoomLight.on(), ()->livingRoomLight.off());
        remote.onButtonWasPressed(0); // 리시버에 있는 행동 메서드 수행
        remote.offButtonWasPressed(0);
        
    }
}
  • 리모컨(인보커) 슬롯마다 커맨드 객체가 할당된다.
  • 사용자가 버튼을 누르면 해당 Command 객체의 excute()메서드를 호출한다.
  • execute() 메서드는 리시버에게 특정 작업을 처리하도록 지시한다.

작업 취소 기능 추가

Command Class

  • Command 클래스 내에 작업취소 기능을 수행할 메서드를 추가한다.
public interface Command{
	public void execute();
    public void 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 on 이전 상태이므로 off
    }
}

Receiver Class

  • undo 시에 처리할 행위를 저장한 메소드를 생성한다.

Invoker Class

  • Invoker 클래스에서 undo 명령을 저장할 인스턴스를 추가한다.
  • Command 객체에게 undo 명령을 요청할 메서드를 생성한다.
public class RemoteControl {
	// on, off 명령어를 저장할 배열
	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;
	}
  
  	// 명령어 저장 메서드 - slot 번호에 on,off 커맨드 객체 저장
	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]; // 해당 객체를 undoCommand 레퍼런스 변수로 저장
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
        undoCommand = offCommands[slot];// 해당 객체를 undoCommand 레퍼런스 변수로 저장 
	}
    
    //undo 버튼 클릭할 시
    public void undoButtonWasPushed(int slot) {
		undoCommand.undo(); // 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");
		}
		return stringBuff.toString();
	}
}

매크로 커맨드

  • 커맨드들을 저장해서 일련의 행동을 한번에 처리할 수 있다.
public class MacroCommand implements Command {
	Command[] commands;
    
    // 커맨드들을 받아 저장
    public MacroCommand(Command[] commands) {
    	this.commands = commands;
    }
    
    // 커맨드들을 순서대로 처리
    public void execute() {
    	for(int i = 0; i<commands.length; i++) {
        	commands[i].execute();
        }
    }

}

활용

스케쥴러, 스레드 풀, 작업 큐

  • computation의 한 부분(Receiver와 일련의 행동)을 패키지로 묶어서 일급 객체 형태로 전달할 수 있어, 오랜 시간이 지나도 computation을 호출할 수 있다.
  • 작업 큐
    • 커맨드 인터페이스를 구현하는 객체를 큐에 추가한다.
    • 컴퓨테이션을 고정된 개수의 스레드로 제한할 수 있다.
    • 스레드는 큐에서 커맨드를 하나씩 제거하면서 커맨드의 excute() 메소드를 호출한다. 메소드 실행이 끝나면 다시 큐에서 새로운 커맨드 객체를 가져간다.

로그 기록

  • 어떤 명령을 실행하면서 디스크에 실행 히스토리를 기록하고, 애플리케이션이 다운되면 커맨드 객체를 다시 로딩해서 excute() 메소드를 자동으로 순서대로 실행한다.
  • 스프레드시트 애플리케이션
    • 데이터가 변경될 때마다 체크포인트 이후의 모든 행동을 로그에 기록하는 방식으로 복구시스템을 구축하였다.
profile
개발자가 되기 위해 성장하는 중입니다.

0개의 댓글