Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

기록하자..

명령 패턴(Command Pattern) 본문

디자인패턴공부

명령 패턴(Command Pattern)

P23Yong 2021. 11. 28. 21:26

명령 패턴

  • 요청을 객체로 캡슐화 해주며 파라미터를 통해 다양한 요청을 처리할 수 있도록 해준다.
    • 요청을 큐에 유지
    • 요청을 저장
    • 요청 취소(undo) 기능 제공
  • 연산 수행을 요청하는 객체와 그 요청을 실제 수행하는 객체의 연결을 느슨하게 해준다.

기존의 작업 수행 VS 명령 패턴

Sender ~ Receiver를 통한 명령 수행

 

Sender ~ Invoker/Command Manager ~ Receiver

Sender : 명령의 실행 요청, 명령 객체의 생성/등록
Invoker/Command Manager : 등록되어 있는 명령 객체를 Receiver에게 전달
Receiver : 명령 실행

 

기존 Sender의 작업 수행 방식의 코드이다.

public class Sender {
    void bar(Receiver r) {
        r.foo();
    }
}

 

Command 객체를 통한 작업 수행 방식의 코드이다.

public interface Command { void execute(); }
public class Invoker {
    private Command c;
    public void setCommand(Command c) {
        this.c = c;
    }

    void request() {
        c.execute();
    }
}

 

public class Sender {
    void bar(Invoker invoker) {
        invoker.request();
    }
}

HeadFirstDesignPattern에서는 한 개의 만능 리모컨(Reomte Control App)으로 여러 전자제품들을 조작할 수 있도록 요청하는 예제를 들고 있다.

리모컨은 연동해야 할 것들에 대해 모르고 있어도 연동할 수 있도록 설계해야 한다.

하지만 모르면 어떻게 동작시킬 수 있을까? 해결할려고 조건문을 많이 사용하는 것도 바람직하지는 않다.

이때 사용하는 것이 리모컨과 제품들 사이에 중계자를 두어 해결하는 패턴인 command 패턴이다.

 

명령 패턴의 간단한 소개

HeadFirstDesignPattern에서는 식당을 예로 들고 있다.

  1. 고객(Client)는 웨이터(Invoker/CommandManager)에게 주문을 한다
  2. 웨이터는 주문들을 받아 카운터에게 주며, 이 정보는 주방장(Receiver)에게 전달 된다.
  3. 주방장은 명령대로 음식을 준비한다.

그림으로 나타내보면

  • 명령을 생성하고 등록하는 자와 후에 해당 명령을 요청하는 자는 다를 수 있다.
  • 명령객체는 처리자가 수행해야하는 절차를 추상화하고 있는 execute()메서드를 제공한다.
  • Invoker에 등록된 명령은 후에 재사용할 수도 있고, 버려질 수도 있다.

이렇게 보면 Command 패턴은 요청을 객체로 캡슐화 해준다. 이를 통해 요청을 인자로 전달하거나 큐에 유지, 실행한 요청을 보관해 후에 재생, 요청을 취소할 수 있도록 해준다.

undo

undo는 execute를 통해 실행된 결과를 되돌려야 한다.

  • CommandManager에 undoStack을 유지

 

public class CommandManager {
    private Stack<Command> undoStack = new Stack<>();

    public void execute(Command... commands) {
        for(Command command : commands) {
            undoStack.push(command);
            command.execute();
        }
    }

    public void undo() {
        if(!undoStack.isEmpty()) {
            Command command = undoStack.pop();
            command.undo();
        }
    }
}

 

undo의 문제점

undo가 마지막 명령을 취소하는 것이 아니라 위의 CommandManager처럼 명령 객체를 유지하여 실행할 때 문제가 발생할 수 있다.

LightOnCommand
LightOffCommand
LightOnCommand
LightOffCommand
위와 같이 스택에 저장되어 있는 객체 중 2개는 같은 객체이다. 4개를 연속하여 실행하면 문제가 없다.

그런데
CeilingFanMediumCommand
CeilingFanHighCommand
CeilingFanMediumCommand
CeilingFanHighCommand
위와 같은 경우 prevSpeed를 통해 이전 속도를 유지한다 생각해보자

첫 번째로 Medium을 실행했을 때 prevSpeed = off 이고
두 번째로 High를 실행했을 때 prevSpeed = medium 이다.
세 번째로 Medium을 실행했을 때 prevSpeed는 = medium이지만 이 때 new를 이용해 새로운 Command객체를 만든 것이 아니라면 첫 번째 실행했을 때 가지고 있던 prevSpeed를 똑같이 참조하고 있다는 것이다.

이러면 세 번째 Command에서 prevSpeed는 off가 아닌 medium이 되어버리는 문제가 발생한다.
이를 해결하는 방법은 명령 객체를 복제하여 스택에 저장, 매번 새롭게 생성할 수 있겠다.

여기까지 명령 패턴에 대해 공부해보았다.