따봉도치야 고마워

Head First Design Patterns : (6)커맨드 패턴 본문

프로그래밍/공부

Head First Design Patterns : (6)커맨드 패턴

따봉도치 2020. 9. 17. 16:37

커맨드 패턴

  • 커맨드 패턴을 이용해 요청 내역을 객체로 캡슐화 할 수 있고, 매개변수를 사용해 여러 가지 다른 요청을 집어 넣을 수 있다.
  • 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수 있고, 작업취소 기능도 지원이 가능하다.
  • 요청을 하는 객체와(요청), 그 요청을 수행하는 객체(동작)를 분리 할 수 있음.
  • 리시버에 대한 동작이 추가될 때마다 잡다한 클래스가 많이 추가될 수 있다는 단점이 있음.
  • 요청 자체를 리시버한테 넘기지 않고 자신이 처리하는 '스마트' 커맨드 객체도 종종 사용

 

커맨드 패턴 구조

  1. 수신자 (Receiver) : 실제 행동을 하는 객체
  2. 커맨드 (Command) : 리시버와 리시버의 특정 행동을 호출하는 객체, 버튼에 할당될 행위
  3. 발동자 (Invoker) : 커맨드를 저장해놓고, 행위 발동 시 커맨드 호출
  4. 클라이언트 (Client) : 커맨드 객체를 생성, Invoker에 커맨드 객체를 할당
  • 클라이언트는 커맨드 객체를 생성하고, 리시버를 설정해줌
  • Invoker는 커맨드 객체를 저장하고, 해당 커맨드의 excute() 메소드를 호출해 작업을 요청함
  • 커맨드 객체에선 리시버와 특정 작업을 연결해줌. excute() 실행 시 리시버에 있는 메소드를 호출하게 됨.

 

활용

- 요청을 에 저장 : 커맨드를 작업 큐에 저장 후, 나머지 한 쪽에선 스레드가  하나씩 작업을 받아 수행한다. (excute)

- 요청을 로그에 저장 : 커맨드에 store(), load() 메소드 추가 후, 각 커맨드가 실행될 때 디스크에 내역을 저장하고, 시스템 다운 시 복구한다.


객체 마을 식당 예제

  • 손님(클라이언트)이 주문을 하면 주문서(커맨드 객체)에 메뉴 항목이 캡슐화됨
  • 주문서에는 orderUp() 메소드와 누가 처리할 것인지(리시버)에 대한 정보가 있음
  • 웨이터는 해당 주문서에 있는 orderUp() 메소드만 호출해줌.

리모컨 API 설계 예제 - 커맨드 패턴 적용

- 리모컨 버튼 별로 각각 장치 제어 기능을 추가하려고 한다.

- 리모컨 버튼 코드에 일일히 각 장치 제어 기능을 달아주게 되면, 장치가 추가/제거/변경 될 때 마다 리모컨 코드를 수정해줘야 한다.

- 커맨드 패턴을 이용해, 리모컨에선 커맨드 객체만 가지고 있고 실제로 어떤 작업이 처리되는지는 모르게 한다.

 

<커맨드 객체 만들기>

1) Command 인터페이스 생성

public interface Command{
	public void excute();
}

 

2) 전등을 켜기 위한 커맨드 클래스 구현

public class LightOnCommand implements Command{
    Light light;
    
    public LightOnCommand(Light light){
    	this.light = light;
        //여기서 light가 리시버 객체가 됨
    }
    
    public void excute(){
    	light.on();
    }
}

 

<커맨드 객체 사용하기>

public class RemoteControl{
//invoker 역할을 할 클래스
    Command[] onCommands;
    Command[] offCommands;
    
    public RemoteControl(){
    	onCommands = new Command[7];
        offCommands = new Command[7];
        
        Command noCommand = new NoCommand();
        //null 객체를 따로 생성해서 초기화 해주면 클라 쪽에서 null check 안해도 됨
        for(int i=0; i<7; i++){
        	onCommand[i] = noCommand;
            offCommand[i] = noCommand;
        }
    }
    
    public void setCommand(int slot, Command onCommand, Command offCommand){
    	onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    
    public void onButtonWasPressed(int slot){ 
    //on 버튼 눌릴 때 호출될 메소드
    	onCommand[slot].excute();
    }  
    
    public void offButtonWasPressed(int slot){ 
    //off 버튼 눌릴 때 호출될 메소드
    	offCommand[slot].excute();
    } 
}
  • 버튼이 하나인 리모컨 코드
  • 클라이언트에서 해당 클래스의 객체(Invoker)를 만들어 2)커맨드 객체를 세팅 및 명령 요청

 

 

리모컨 API 설계 예제 - Undo 기능 추가

  1. Command 인터페이스에 undo() 메소드 추가
  2. 각 커맨드 객체에 excute()와 반대되는 행동을 하는 undo() 메소드 구현
    • 상태 저장이 필요한 경우라면 상태 변수 추가해 구현
    • ex) 선풍기 속도 변경
  3. RemoteControl에 가장 마지막으로 누른 버튼을 기록하고, undo버튼을 처리하는 부분 추가
public class RemoteControlWithUndo{
	Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;
    
    public RemoteControl(){
    	..
        
        undoCommand = noCommand;
    }
    
    ..
    
    public void onButtonWasPressed(int slot){ 
    	onCommand[slot].excute();
        undoCommand = onCommand[slot];
    }  
    
    public void offButtonWasPressed(int slot){ 
    	offCommand[slot].excute();
        undoCommand = offCommand[slot];
    } 
    
    //undo 버튼 클릭 시 마지막으로 눌렀던 버튼(커맨드)의 undo() 메소드 호출
    public void undoButtonWasPushed(){
    	undoCommand.undo();
    }
}

 

 

리모컨 API 설계 예제 - Macro 기능 추가 (여러기능 한 번에)

  1. 여러 가지 커맨드를 한 번에 실행할 수 있는 새 커맨드 생성
  2. ON/OFF 커맨드들을 생성하고 배열로 만들어 MacroCommand에 넣어줌
  3. MacroCommand를 버튼에 할당
//1
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].excute();
        }
    }
}
Command[] partyOn = {lightOn, stereoOn, tvOn, hottubOn};
Command[] partyOff = {lightOff, stereoOff, tvOff, hottubOff};

//2
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);

//3
remoteControl.setCommand(0, partyOnMacro, partyOffMacro);

 

NOTE

- null 객체 활용하기

Comments