07. 커맨드 패턴

AlmondGood·2022년 7월 20일
0

디자인패턴

목록 보기
8/16
post-thumbnail

커맨드 패턴(Command Pattern)

커맨드란 단어는 명령을 뜻하죠. 명령이 이루어지려면 명령을 내리는 사람명령을 받는 사람이 있어야 합니다.

커맨드 패턴이란, 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 메서드 이름, 매개 변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴입니다.


커맨드 패턴이 이루어지려면, 4가지의 객체가 필요합니다.

  • 사용자(Client) : 명령을 지시하는 객체
  • 명령(Command) : 하위 클래스들에서 구현해야 하는 명령 메소드가 담긴 인터페이스
  • 호출자(Invoker) : 명령을 받고, 수신자에게 명령을 전달
  • 수신자(Receiver) : 구체적인 명령을 실행할 객체

※ 이 글에서는 한글과 영어를 혼용해서 사용하니 단어를 알아두시기 바랍니다!

사용자가 구체적인 명령 객체를 생성하고, 명령을 실행하면,
호출자가 명령을 전달하게 되고, 수신자는 명령을 받아 각각의 구체적인 명령을 실행하게 됩니다.



커맨드 패턴 구현

에어컨을 조종하는 리모콘을 만든다고 생각해봅시다.
일단 처음이니 전원부터 만들어 봐야겠네요.

// 명령 인터페이스
interface Command {
    void execute();
}

// 에어컨 리모콘(인보커)
class RemoteControl {
	// 명령을 행동 단위로 분류
    Command onCommand;
    Command offCommand;

    // 명령 저장
    public void setCommand(Command onCommand, Command offCommand) {
        this.onCommand = onCommand;
        this.offCommand = offCommand;
    }

    public void powerOn() {
        onCommand.execute();
    }

    public void powerOff(){
        offCommand.execute();
    }
}

// 리시버
class Power {

    void on() {
        System.out.println("에어컨 전원이 켜졌습니다.");
    }

    void off() {
        System.out.println("에어컨 전원이 꺼졌습니다.");
    }
}

// 구체적인 명령 클래스
class PowerOnCommand implements Command {
	Power power;

	PowerOnCommand(Power power) {
    	this.power = power;
    }

    @Override
    public void execute() {
        power.on();
    }
}

// 구체적인 명령 클래스
class PowerOffCommand implements Command {
	Power power;
	
	PowerOffCommand(Power power) {
    	this.power = power;
    }

    @Override
    public void execute() {
    	power.off();
    }
}

// 사용자
public static void main(String[] args) {
	// 인보커 생성
	RemoteControl remocon = new RemoteControl();

    // 리시버 생성
    Power power = new Power();

    // 리시버에게 명령을 받을 준비
    PowerOnCommand powerOn = new PowerOnCommand(power);
    PowerOffCommand powerOff = new PowerOffCommand(power);

    // 명령 저장
    remocon.setCommand(powerOn, powerOff);

    // 사용자로부터 명령 지시, 실행
    remocon.powerOn();		// 에어컨 전원이 켜졌습니다.
    remocon.powerOff();		// 에어컨 전원이 꺼졌습니다.
}

명령이 실행되는 과정을 따라가볼까요?

// 사용자가 명령 지시(인보커 호출)
remocon.powerOn();// 인보커가 명령 호출
onCommand.execute();// PowerOnCommand 클래스로부터 리시버에게 명령 전달
power.on();// 리시버가 명령 실행
System.out.println("에어컨 전원이 켜졌습니다.");

사용자 → 인보커 → 명령 전달 → 리시버 순으로 객체가 호출되고, 명령이 실행됩니다.

그런데 코드가 어디선가 본 것 같지 않나요?
전략 패턴에서 사용한 코드와 무언가 비슷해 보입니다.
이에 대해선 이후에 이야기 해보기로 하겠습니다.



커맨드 패턴 응용

1. 여러 커맨드 사용(배열)

리모콘에는 버튼이 전원 버튼만 있는 것은 아니죠.
온도 조절 버튼도 있겠고, 바람 세기 조절 버튼도 있겠습니다.

// 에어컨 리모콘(인보커)
class RemoteControl {
	// 명령을 행동 단위로 분류
    // 커맨드 배열로 만들어 여러 가지 명령(버튼) 생성
    Command[] onCommand; 		// 편의상 상승 버튼 겸함
    Command[] offCommand; 		// 하강 버튼 겸함

    // 명령 저장
    // 원하는 버튼에 명령 저장
    public void setCommand(int button, Command onCommand, Command offCommand) {
        onCommand[button] = onCommand;
        offCommand[button] = offCommand;
    }

	// 원하는 버튼 실행
    public void powerOn(int button) {
        onCommand[button].execute();
    }

    public void powerOff(int button){
        offCommand[button].execute();
    }
}

이런 식으로 커맨드를 배열로 만들면 여러 커맨드를 담을 수 있게 됩니다.
0번 인덱스(버튼)는 전원, 1번 인덱스는 온도 조절, 2번 인덱스는 바람 세기 조절 처럼요.

그러면 전원 리시버와 전원 켜기/끄기 클래스를 만들어 준 것 처럼
각 리시버와 구체 명령 클래스들도 만들어 줘야 합니다.


2. 실행 취소(로깅)

커맨드 패턴의 정의에서 로깅하고 취소할 수 있는 패턴이라고 했었죠.
커맨드 패턴을 활용하면 로그를 만들 수 있습니다.

class RemoteControl {
    Command[] onCommand; 		
    Command[] offCommand; 		
	
	// 한 번만 기록되는 로그
	Command log;

    public void setCommand(int button, Command onCommand, Command offCommand) {
        onCommand[button] = onCommand;
        offCommand[button] = offCommand;
    }

    public void powerOn(int button) {
        onCommand[button].execute();
        // 로그 저장
        log = onCommand[button];
    }

    public void powerOff(int button){
        offCommand[button].execute();
        // 로그 저장
        log = offCommand[button];
    }
    // 로그를 활용한 실행 취소
    public void undo() {
    	log.undo();
    }
}

인보커가 명령을 호출할 때 객체 하나를 만들어 객체에 그 명령을 저장하면 로그 객체가 됩니다.
실행 취소를 하고 싶다면, 각 구체 명령 클래스들에 undo() 메소드를 구현하여 반대되는 명령을 실행시키면 됩니다.

// PowerOnCommand 클래스
void undo() {
	power.off();
}
// PowerOffCommand 클래스
void undo() {
	power.on();
}

만약 로그를 여러 개 저장하고 싶다면 스택을 사용하고, 대신 로그에 push()하면 됩니다.

Stack<Command> log = new Stack<>();
...
log.push(onCommand[button]);
...
log.push(offCommand[button]);
...



전략 패턴  vs  커맨드 패턴

전략 패턴커맨드 패턴은 중간에 '리시버'가 있다는 것 말고는 매우 유사해 보입니다.
하지만 용도에서 차이가 있습니다.

  • 전략 패턴'어떻게' 동작을 수행하냐에 중점을 두고,
  • 커맨드 패턴'무엇을' 동작하냐에 중점을 둡니다.
  • 전략 패턴은 먼저 어떻게 라는 측면에 집중하게 됩니다. 하고자 하는 것은 이미 정해져 있고, 방법을 어떻게 할지에 대한 유연성을 고려하며 구현합니다.
    인터페이스의 메소드에 직접적으로 의존을 하게 되어서, 해당 메소드의 parameter들에 강하게 영향을 받습니다.

  • 커맨드 패턴은 무엇을 초점을 두게 됩니다. 어떻게 할지에 대한 방법은 외부에서 정의하며 주입을 해주며, 그것을 실행하는 것이 중요하기 때문입니다.

https://tecoble.techcourse.co.kr/post/2021-10-04-strategy-command-pattern/



참고 자료

https://tecoble.techcourse.co.kr/post/2021-10-04-strategy-command-pattern/

profile
Zero-Base to Solid-Base

0개의 댓글