BackEnd/Design Patterens

[Design Patterns] Command Pattern : 커맨드 패턴

샤아이인 2022. 1. 13.

Head First Design Patterns 책을 읽으며 정리한 내용입니다. 문제가 될 시 글을 내리도록 하겠습니다!

Command Pattern 이란?

커맨드 패턴 - 커맨드 패턴을 이용하면 요구사항을 객체로 캡슐화 할 수 있으며,
매개변수를 사용하여 다양한 요구사항을 전달할 수 있게됩니다.
또한 요청 내용을 큐에 저장하거나 로그로 기록할수도 있으며, 작업되돌리기 또한 가능합니다.
 

우선 커맨드 패턴의 개념부터 알아봐야 겠죠? 코드보기 전에 간단한 이야기 먼저 들려드리죠~

 

식당에서 음식을 주문한다고 해봅시다. 주문과정을 요약하면 다음과 같을 것 입니다!

1) 고객이 종업원에게 주문을 합니다.

2) 종업원이 주문을 받고 주방에 주문을 전달합니다.

3) 주방장이 주문대로 음식을 준비합니다

 

이 과정을 조금만 더 자세하게 살펴봅시다!

 

1) 우선 고객이 createOrder() 을 호출하게 됩니다. 이때 order 객체가 생성됩니다!

2) 종업원이 takeOrder() 을 호출하여 order객체를 받은후, 다시 주방에 전달하기 위해 orderUp() 을 호출합니다.

3) order 객체에는 음식을 준비하기위해 필요한 요구사항들이 저장되어있습니다.

4) order 객체가 주방장에게 makeBurger(), makeShake() 와 같은 메소드를 호출하여 지시합니다.

5) 주방장은 전달받은 지시사항을 준수하면서 음식을 만듭니다!

 

  •  주문서는 주문한 메뉴를 캡슐화 합니다.

주문서는 주문한 메뉴를 요구하는 객체라 할 수 있습니다.

이 객체에는 orderUp() 이라는 식사를 준비하기 위한 행동을 캡슐화한 메소드가 있습니다.

또한 어떤 객체에게 이 주문을 전달해야할지에 대한 레퍼런스 또한 갖고있을 것 입니다.

주문 객체 안의 내용은 캡슐화 되어있기 때문에 중간에 전달하는 종업원은 어떤내용이 적혀있는지 모릅니다. 그냥 주방장에게 전달한후, 주문이 들어왔음을 알려주기만 하면 됩니다!

 

  • 종업원은 단순히 orderUp() 메소드를 호출할 뿐 입니다!

진짜 단순히 orderUp()을 호출하여 식사를 준비하면 끝 입니다!

어떻게 준비되는지 까지 알필요는 없습니다. 그냥 이 메소드를 호출하면 준비되는 것 만 알고있으면 끝 입니다!

 

takeOrder() 메소드는 단순히 여러 주문객체를 인자로 받아들이는 역할을 합니다.

 

  • 식사를 만드는 방법은 주방장만이 알고있습니다!

종업원이 orderUp()을 호출하면 주방장이 그 주문을 받아 음식을 만들기 위한 메소드를 전부 처리합니다.

여기서 핵심은 주방장과 종업원이 완전히 분리되있다! 는 점 입니다. 바로 주문서(주문객체) 덕분입니다!

 

한마디로 어떤것을 요구하는 객체와 그 요구를 받아들이고 처리하는 객체를 분리시키는 디자인 입니다!

 

 

 

Clinet에서 command객체를 생성하고, setCommand()를 통하여 invorker에 command객체를 저장합니다.

이후 client에서 invoker에게 excute()를 실행해 달라 요청하면 invorker에서 명령객체의 excute()를 실행하고 Receiver에 있는 특정 메소드들이 호출됩니다!


패턴 소개

우선 간단한 command interface를 정의해 봅시다!

public interface Command {
    public void execute();
}
 

이제 전등을 켜기위해 lightOnCommand를 구현해 봅시다! 다음과 같이 구현하면 될거에요!

public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute(){ // 리시버 객체에 있는 on()을 호출
        light.on();
    }
}
 

lightOnCommand의 인자로 이 커맨드 객체로 제어할 특정 전등에 대한 정보를 넘겨줍니다.

 

이번에는 invoker역할을 담당하는 SimpleRemoteControl 을 살펴봅시다!

public class SimpleRemoteControl{
    Command slot;

    public SimpleRemoteControl() {}

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

    public void buttonWasPressed(){ // 버튼이 눌리면 커맨드객체의 execute() 호출
        slot.execute();
    }
}
 

 

마지막으로 다음과 같이 SImpleRemoteControl을 사용하는 테스트를 해봅시다!

public class Main {
    public static void main(String[] args) {
	    SimpleRemoteControl remote = new SimpleRemoteControl();
	    Light light = new Light();
	    LightOnCommand lightOn = new LightOnCommand(light);

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

위의 코드에서는 remote 객체가 invoker가 됩니다.

 

이후 light 객체를 생성하고 이 객체를 기반으로 명령객체를 생성한 후, 생성된 명령객체를 invoker인 remote에 등록합니다.

 

이후 사용자로부터 "불 켜줘!" 라는 명령을 invorker가 받으면 buttonWasPressed()가 실행되고, 명령객체.execute() 가 실행되어 recevier(light)의 불이 켜지게 됩니다.

 

위 코드의 실행결과는 다음과 같습니다!

 

커맨드 객체는 일련의 행동을 특정 receiver하고 연결시킴으로써 요구사항을 캡슐화 한 것 입니다!

 

행동에 해당하는 메소드 들과 최종적으로 조종할 receiver를 한 객체안에 집어넣고, 오직 execute() 라는 메소드 하나만을 외부에 공개하게됩니다. 이 메소드를 호출하면 receiver에 작업이 수행됩니다.

외부에서는 어떤객체가 receiver 역할을 하는지? 어떤일을 수행하고있는지? 전혀 몰라도 되는 것 입니다.

 

종업원(Invoker) 입장에서는 특정인터페이스만 구현되어있다면 그 커맨드객체에서 실제로 어떤일을 하는지는 알필요가 없습니다. 따라서 invorker에 setCommand()를 통하여 여러 명령객체를 전달하면 buttonWasPressed() 를 호출할 뿐 입니다.


위에서 보여드렸던 클래스 다이어그램을 다시한번 살펴봅시다!

▶Client

구체적인 명령객체를 생성하고 Receiver를 등록합니다.

 

▶Invoker

인보커에 명령객체가 들어있으면 execute()를 호출함으로써 명령객체에게 특정 작업을 수행해달라 요청합니다.

 

▶Command

커맨드는 모든 커맨드 객체에서 구현해야하는 인터페이스 입니다. execute()를 이용하면 receiver에 특정 작업을 처리하라는 명령을 전달하게 됩니다.

 

▶ConcreteCommand

특정 행동과 receiver를 연결해줍니다. 인보커에서 execute()를 호출하면 ConcreteCommand 객체에서 리시버에 있는 메소드를 호출함으로써 작업을 처리합니다.

 

▶Receiver

최종적으로 요구사항을 전달받아 수행하는 역할을 합니다. 자신이 어떤일을 해야하는지 알고있습니다.

댓글