커맨드 패턴의 정의
커맨드 패턴을 이용해 메소드 호출을 캡슐화해서, 계산 과정의 각 부분들을 결정화 시킨 다음
작업을 요청한 쪽 하고 작업을 처리하는 쪽을 분리 시킬 수 있다.
즉, 계산하는 코드를 호출하는 객체에서는, 해당 일을 어떻게 처리해야 하는지에 대해 신경 쓰지 않아도 된다.
식당 주문 예제
커맨드 패턴에 대해 위의 세 문장으로 바로 이해할 수 있는 사람은 거의 없을 것이다.
좀 더 알기쉽게 유명한 예제인 식당에서 주문을 하는 예제로 이해해보자.
식당을 이루고 있는 객체는 고객, 주문서, 종업원, 주방장 총 4개라고 가정해보자.
- 고객은 주문서에 자신이 원하는 메뉴를 적는다.
- 종업원은 주문서에 적힌 메뉴를 확인한다.
- 주문서에 적힌 메뉴를 확인하고 이 메뉴를 주방장에게 전달한다.
이 3가지 Process로 식당은 운영될 것이다.
이때, 중요한 특징을 짚고 넘어가자면
- 종업원은 음식을 만드는 방법을 몰라도 된다.
- 주방장은 음식을 만드는 방법을 알고 있다.
- 주방장은 어느 종업원이 주든 상관없이, 받은 주문서 대로 음식을 만들 뿐이다.
이 정도가 있는데, 이래도 식당이 돌아가도록 해주는 객체는 바로 주문서이다.
이 주문서가 이번 포스트에서 다룰 커맨드 패턴에 해당되는 객체이다.
식당 주문 예제 코드
주문서의 이름을 Command로 바꾸고 음식을 만드는 메소드를 추가해보자.
public interface Command // 주문서에 해당된다.
{
publiuc void Execute(); // 음식을 만드는 메소드
}
주방장은 온지 얼마 되지않아 피자랑 파스타만 만들 수 있다.
public class Chef
{
public void MakePizza()
=> print("피자 완성이요");
public void MakePasta()
=> print("파스타 완성이요");
}
피자 주문과 파스타 주문은 다음과 같을 것이다.
public class PizzaCommand : Command
{
Chef chef;
public PizzaCommand(Chef chef)
{
this.chef = chef;
}
public void Execute()
{
chef.MakePizza();
}
}
public class PastaCommand : Command
{
Chef chef;
public PastaCommand(Chef chef)
{
this.chef = chef;
}
public void Execute()
{
chef.MakePasta();
}
}
종업원은 각종 주문서(Command)들을 가지고 있을것이 분명하다.
public class Waiter
{
List<Command> commandList;
public void SetCommand(Command command)
{
commandList.Add(command);
}
public void OrderUp() // 현재까지 확인한 주문서들을 주방장에게 넘겨준다
{
for(int i=0; i<commandList.Count; i++)
commandList[i].Execute(); // Command 인터페이스에는 Execute 메소드가 존재
}
}
종업원(Waiter)은 음식을 어떻게 만드는지도 모르고 누가 주문했는지도 모른다. 심지어 주방장의 존재도 모른다!
다만, OrderUp 메소드가 호출되기만 하면 약속된 인터페이스를 상속받은 Command들의 Execute 메소드만
호출하면 된다(결과적으로는 주방장에게 넘겨줌).
최종적으로 음식을 시키는 Client는 다음과 같이 음식을 시켜야 할 것이다.
public class Client
{
public static void Main()
{
Waiter waiter = new Waiter();
Chef chef = new Chef();
PizzaCommand pizza = new PizzaCommand(chef);
waiter.SetCommand(pizza);
waiter.OrderUp();
}
}
과연 주문서(Command)가 필요한 것인가
위의 코드가 가져다 주는 이점이 명확히는 아직 안보인다.
식당의 Process를 바꿔 주문서를 없애고
고객, 종업원, 주방장 이렇게 3가지 객체만 존재한다고 생각해보자.
1. 고객은 종업원에게 자신이 원하는 메뉴를 말한다.
2. 종업원은 고객이 말한 메뉴를 머리속에 기억한다.
3. 머리속에 있는 메뉴를 기억해서 해당 메뉴의 이름을 주방장에게 직접 말한다.
다시 코드로 옮겨보자
여전히 주방장은 두가지 메뉴만 만들수 있다.
public class Chef
{
public void MakePizza()
=> print("피자 완성이요");
public void MakePasta()
=> print("파스타 완성이요");
}
웨이터는 더 이상 주문서를 가지고 다닐수 없어
어쩔 수 없이 주문들을 외우고 다녀야 한다.
public class Waiter
{
Chef chef;
List<String> foodList;
public Waiter(Chef chef)
{
this.chef = chef;
}
public void OrderFood(String foodName)
{
foodList.Add(foodName);
}
public void OrderUp() // 현재까지 외운 음식들을 주방장에게 알려준다.
{
for(int i=0; i<foodList.Count; i++)
{
if(foodList[i] == "Pizza")
chef.MakePizza();
else if(foodList[i] == "Pasta")
chef.MakePasta();
}
}
}
이제 Waiter는 Chef를 멤버변수로 가지며, 각 음식별로 Chef의 어떤 메소드를 호출 할지 미리 알고있어야 한다.
이는 Waiter와 Chef가 직접 연결되어 있다고 볼 수 있다.
물론 위의 예시는 억지로 String 비교를 하고 있긴 하지만, Chef의 어떤 메소드를 호출 할지는
Waiter안에서 결정하므로 강하게 결합되어 있다고 말할 수 있다.
만약 Pizza나 Pasta를 만드는 메소드가 변경되면 Waiter와 Chef 둘 다 바꿔주어야 할 것이다.
이는 우리가 추구하는 Loose-Coupling에 부합하지 않는다는것을 알 수 있다.
주문서는 우리에게 무엇을 해주었는가
다시 맨 앞의 커맨드 패턴의 정의로 돌아와보자.
커맨드 패턴을 이용해 메소드 호출을 캡슐화해서(인터페이스를 구현해 Execute 메소드를 가지게 강제 했다.)
계산 과정의 각 부분들을 결정화 시킨 다음(Execute 메소드의 구현은 각 Command 마다 다르다)
작업을 요청한 쪽 하고 작업을 처리하는 쪽을 분리 시킬 수 있다. (Waiter와 Chef 클래스는 완전히 분리되었다.)
즉, 계산하는 코드를 호출하는 객체에서는, 해당 일을 어떻게 처리해야 하는지에 대해 신경 쓰지 않아도 된다.
(Waiter는 음식들 마다 따로 처리 해주지 않고 Command로 묶어서 Execute할 뿐이다.)
커맨드 패턴을 이용해 Loose-Coupling을 구현하였다.
'Programming > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 5. 싱글턴 패턴 (0) | 2022.03.22 |
---|---|
[디자인 패턴] 4. 팩토리 패턴 (0) | 2022.03.17 |
[디자인 패턴] 3. 데코레이터 패턴 (0) | 2022.03.17 |
[디자인 패턴] 2. 옵저버 패턴 (0) | 2022.03.10 |
[디자인 패턴] 1. 스트래티지 패턴 (0) | 2022.03.10 |