본문 바로가기
컴퓨터과학/디자인패턴

[디자인패턴] 커멘드 패턴 (Command Pattern)

by 윤호 2021. 12. 6.

목적

요구사항(요청, 명령)을 객체로 캡슐화시킨다.

이를 통해 요구사항을 큐에 넣거나 로그를 남길 수 있으며 작업 취소 기능을 지원할 수도 있다.

사용하려₩는 객체가 많고, API가 서로 다른 경우에 사용한다.

요소

문제 : 사용 객체의 API가 서로 다름

해결 : 실행과 요청을 분리

결과 : 작은 클래스가 많아지지만, 객체 사용에 필요한 복잡성을 제거하고 감춤 (함수 이름이 동일해짐)

정의

커멘드 패턴 클래스를 가정집 리모컨 사용으로 비유

  • Client : 리모컨 버튼의 기능을 인지하고 버튼을 누름
  • Command : 버튼에 실제 사용 객체를 연결해 놓음
  • Invoker : 리모컨 버튼을 누르면 기능을 실행함
  • Receiver : TV, 전등 같은 실제 객체

Command

  • Receiver를 알고 있고, Recevier의 메소드를 호출
  • Recevier의 메소드에서 사용되는 매개변수의 값들은 Command에 저장됨

Receiver

  • 실제 명령 수행

Invoker

  • 요청을 받아서, 요청을 실행하기 위해 Command 인터페이스 연결
  • Command가 실제로 어떻게 실행되는지 모름

Client

  • 무엇을 요청할지 결정
  • 요청 Command를 Invoker에 넘김 

커멘드 패턴을 이용한 가정집 리모컨 - v1. 버튼이 하나

Command 인터페이스

 public interface Command {
    public void execute();
}

Command 인터페이스 구현체 (전등 켜기)

 public class LightOnCommand implements Command { Light light; // light 객체가 Receiver가 됨
    public LightOnCommand(Light light) { // Light 객체가 Receiver
        this.light = light;
    }
    public void execute() {
        light.on();
    }
}

Receiver 객체 Light

(생략)

 

Invoker (리모컨) 

 public class SimpleRemoteControl {
    Command slot;

    public void setCommand(Command command) {
    	slot = command; 
    }
    public void buttonWasPressed() {
        slot.execute();
    }
}

 

Client - 메인 코드

SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);

remote.setCommand(lightOn);
remote.buttonWasPressed();

 

Recevier와 Command가 추가될 경우 - GarageDoor 추가

SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
GarageDoor garageDoor = new GarageDoor();
LightOnCommand lightOn = new LightOnCommand(light);
GarageDoorOpenCommand garageOpen = new GarageDoorOpenCommand(garageDoor);

remote.setCommand(lightOn);
remote.buttonWasPressed();
remote.setCommand(garageOpen);
remote.buttonWasPressed();

 

커멘드 패턴을 이용한 가정집 리모컨 - v2. 버튼이 여러 개

배열에 커멘드를 저장하고, 사용시엔 slot을 지정해서 메소드 호출

public class RemoteControl {
    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;
        }
    }

    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();
    }
}

 

커멘드 패턴을 이용한 가정집 리모컨 - v3. undo (되돌리기) 버튼 추가

방법

1. 커멘드에 undo 메소드를 추가한다 - LightOn 커멘드라면 undo 메소드의 내용은 light.off()

2. 인보커(리모컨)에 undoCommand 변수를 생성하고, 다른 버튼이 눌리면 가장 최근의 버튼을 여기에 저장한다.

3. 인보커(리모컨)에 undoPush 메소드를 생성하고, undoCommand 버튼에 저장된 커멘드의 undo() 메소드를 실행한다.

 

Command, ConcreteCommand - undo 기능 추가

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

public class LightOnCommand implements Command {
    Light light; // light 객체가 Receiver가 됨

	...

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

 

Invoker - undo 버튼이 추가된 리모컨

public class RemoteControlWithUndo {
	...
    Command undoCommand;

    public RemoteControlWithUndo() {
		...
        undoCommand = noCommand;
    }

	...

    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}

 

댓글