디자인패턴 - Command
아주 간단한 말이지만, 다이어그램을 놓고 예제를 봐도 쉽게 와닿지 않는 어려운 패턴이기도 하다.
위 다이어그램에서 사용한 Invoker, Receiver, Command 등에 대한 이해가 먼저 필요한데...
이를 가장 많이 비유하는 것이 식당에서의 주문에서 조리까지 이루어지는 과정이다.
Receiver 는 요리사이고, Invoker 는 서버이다.
손님(Client) 은 메뉴판을 보거나, 서버(Invoker) 에게 어떤 메뉴가 있는지 묻거나 해서 메뉴 하나를 고른다.
각가의 메뉴(Command)는 어떤 재료로 어떻게 요리하는지에 대한 레시피가 이미 결정되어 있다.
여기에서 손님이 고른 메뉴는 ConcreteCommand 이다.
서버(Invoker)는 손님이 고른 메뉴를 주방에 알려준다(execute).
요리사(Receiver)는 메뉴의 레시피대로 음식을 조리한다(action).
여기까지가 Command 패턴이 다루는 부분이다.
행위 자체는 Receiver 에서 일어나지만, 무엇으로 어떤 행위를 할 것인지는 Command 에 들어있다.
이것이 요청을 ConcreteCommand 라는 객체로 캡슐화한다는 말이 되는 것이다.
이 과정에서 주목할 것은
서버(Invoker)는 요리사(Receiver)가 누구인지 알 필요가 없고,
요리사(Receiver) 역시 서버(Invoker)가 누구인지 알 필요가 없다는 점이다.
이 뜻은 Command 패턴을 사용하여 느슨한 결합(loose coupling) 구조를 지원할 수 있다는 이야기다.
다음은 스타크래프트에서 명령을 받아 수행하는 처리를 Command 패턴에 대입시켜 구성해본 예이다.
유닛(Unit)을 선택하면, 해당 유닛에 사용 가능한 명령(Command) 이 패널에 표시된다.
그 중에 한 명령을 선택(UnitCommand)하고 명령에 필요한 위치를 마우스로 클릭하면
해당 명령(UnitCommand)에 따르는 동작(Action) 이 수행된다.
만일 이동 명령을 내렸다면 GroundMoveAction 에서 구체화된 이동하는 처리가 수행된다.
먼저 Unit은 다음과 같이 구성되어 있다.
protected final String name;
protected Unit(String name) {
this.name = name;
}
public String getName() { return this.name; }
public abstract boolean isCommand1();
public abstract boolean isCommand2();
public abstract Command getCommand1();
public abstract Command getCommand2();
}
void setTarget(int target); // 명령이 내려질 위치를 지정..
void execute(); // 명령 수행
}
private Unit unit;
public Pannel() { this.unit = null; }
public void setUnit(Unit unit) { this.unit = unit; } // 유닛이 선택되었다.
public boolean isCommand1() { // 선택된 유닛이 command1 이 가능한지 확인...
if ( unit != null ) return unit.isCommand1();
return false;
}
public boolean isCommand2() { // 선택된 유닛이 command2 가 가능한지 확인...
if ( unit != null ) return unit.isCommand2();
return false;
}
public Command getCommand1() {
if ( isCommand1() ) return this.unit.getCommand1();
return null;
}
public Command getCommand2() {
if ( isCommand1() ) return this.unit.getCommand2();
return null;
}
}
그럼 이제 실제 수행이 되는 Action 을 구성한다.
void start(Unit unit, int target);
}
public void start(Unit unit, int target) {
// 실제 이동은 여기서 이루어진다....
System.out.println(unit.getName() + " " + target + "까지 이동 시작");
}
}
private Action action;
private Unit unit;
private int target;
UnitCommand(Unit unit) {
this.unit = unit;
this.action = new GroundMoveAction();
}
public void setTarget(int target) { this.target = target; } // 어디로 이동할지...
public void execute() { // 이동 시작
this.action.start(unit,target);
}
}
public Scv() {
super("SCV");
}
public boolean isCommand1() { return true; }
public boolean isCommand2() { return false; }
public Command getCommand1() {
return new UnitCommand(this);
}
public Command getCommand2() {
return null;
}
}
public static void main(String[] args) {
Pannel pannel = new Pannel();
Unit unit = new Scv();
pannel.setUnit(unit); // 유닛을 선택...
// command1 명령 실행
if ( pannel.isCommand1() ) {
Command command = pannel.getCommand1();
command.setTarget(4);
command.execute();
}
if ( pannel.isCommand1() ) {
Command command = pannel.getCommand1();
command.setTarget(7);
command.execute();
}
}
}
예제 소스를 보고 얼마나 이해가 될지 모르겠지만... 대략적인 모습만이라도 알 수 있으면 다행일 것 같다.
앞서 커맨드 패턴의 장점으로 느슨한 결합이라는 말을 했지만, 이외에도 훌륭한 장점들이 있다.
명령과 실행을 분리함으로 인해 실행 전/후에 선처리, 후처리 탑재가 용이하다는 것은 쉽게 알 수 있다.
또, 위 예제에서는 언급되지 않았지만, Unit 은 자신의 상태를 가질 수 있는데...
실행전에 Unit 의 상태를 Command에 잠시 기록하여 둔다면.... undo 기능을 쉽게 구현할 수 있다.
여기서 undo 라는 의미는 Unit을 실행 전 상태로 되돌린다는 의미가 된다.
이 말은 또 다른 말로 Rollback 된다는 말로도 표현 할 수 있다.
즉, Command 패턴을 사용하면 어떠한 행위에 대하여 Trasaction 구현이 가능하다는 말이 된다.
또 다른 활용을 놓고 본다면,
행위는 하나이지만, 이들을 실행시키는 역할을 Command 가 관리함으로 인해
하나의 행위에 대해서 Sync, Async 로 활용이 가능하고 더불어 Queing 도 가능해 진다.
스타크래프트에서 Shift 를 누르고 명령을 이것 저것 주면, 유닛은 해당 명령을 하나씩 하나씩 해나간다.
게임에서는 WayPoint 기능이라고 곧잘 이야기하곤 하는데...
이것이 Command 안에서 행위를 Que에 넣어두고 순차적으로 실행해 나가도록 하는 것으로 생각 해 볼 수 있다.
적합한 예가 되는 소스인지는 잘 모르겠지만...
분명한 것은 명령을 객체화 시키고, 행위와 분리하는 것이 커맨트 패턴의 핵심이 된다는 점을 기억하자..
처리의 결과가 io 형태로 핸들링되기 때문에 좀 다르게 보이지만,
Servlet 도 어떤 의미에서는 Command 패턴에 해당된다.
우리는 doPost(ServletRequest req, ServletResponse res) 를 Override 하는 것으로 구현을 하는데..
ServletResponse 에서 Writer 또는 OutputStream 에 쓰는 것으로 서비스가 구성된다.
Client 에 내용을 전달하는 행위를 하는 ServletResponse 는 Receiver 에 해당된다.
Receiver 가 인수로 전달되는 이유는 Servlet 이 Flyweight 패턴 이기 때문이다.
즉 MultiThreadSafe 를 보장하기 위해서 ServletRespons가 인수로 전달되는 것이다.
또 MacroCommand 라는 말을 들을 수도 있는데..
흩어져 있는 ConcreteCommand 들을 계층적으로 관리하는 구조를 통해 서비스 하는 Command 패턴이라고 이해하면 될 듯 하다.
계층적인 구조라는 것은 다음에 설명할 패턴이다.
'develop > java patterns' 카테고리의 다른 글
| 디자인패턴 연습 6 (0) | 2008/06/25 |
|---|---|
| 디자인패턴 - Command (6) | 2008/06/24 |
| 디자인패턴 - TemplateMethod (0) | 2008/06/24 |
| 디자인패턴 연습 5 (0) | 2008/06/18 |
| 디자인패턴 - Flyweight (3) | 2008/06/18 |
| 디자인패턴 - Singleton (1) | 2008/06/17 |
-
-
오래된꿈읽기 2008/11/10 20:43
일이 몰리는 바람에 State 패턴까지 가지를 못하고 작성이 중지되었는데... Command 패턴은 어떤 행위를 위한 요청을 묶어 놓는다는 개념입니다. 그리고 하나의 행위에 대해 누가 이 일을 할지는 모른다는 것...
식당에 가면 메뉴판을 보여줍니다. 이것은 미리 정의된 행위의 목록입니다. 내가 먹을 것을 골라 웨이터에 말하면 잠시후 요리가 나에게 옵니다. 그것을 어떤 사람이 요리했는지는 중요하지 않습니다.
요리라는 정의된 행위는 레시피로서 존재하는 것이고..
몇번 테이블의 손님이 몇번 요리를 주문했다라는 캡슐화된 요청이 주방으로 가게 되는 것입니다.
스타에서 한 유닛을 선택하면 9개의 메뉴판이 나옵니다. 그중에 이동 명령을 누르면 유닛은 이동합니다.
선택한 유닛, 선택한 명령어와 좌표가 커맨드로 묶여 엔진에 전달되고 엔진은 이를 에니메이션을 보여주면서 해당 좌표로 이동시키게 된다는 점입니다.
State 패턴은 객체의 상태에 따라 행위를 변경할 수 있게 한다는 겁니다...
해처리가 업그레이드되서 레어가 되면..
오버로드 기능을 업그레이드 할 수 있는 명령이 추가됩니다...
즉 건물의 상태가 해처리에서 레어로 상태가 바뀌면..
컨트롤 패널상의 4,5,6 번째 버튼이 활성화되고
이들을 누르면 각각의 업그레이드 명령을 수행하게 되는 거죠...
이해가 되실런지...??
또 Action 과 Command 는 1:1 이 아닙니다. 위 예를 든 GroundMoveAction 은 땅으로 이동하는 모든 유닛이 공유할 수 있는 성격입니다. 즉 GroundMoveAction 은 모든 유닛이 사용할 수 있는 공통된 로직을 담게 됩니다. 유닛별로 미세한 차이(에니메이션, 이동속도등)가 나는 부분은 UnitCommand 를 통해 제공될 수 있겠구요... 유닛과 액션은 커맨드를 통해서 결합되어 지는데.. 이를 느슨한 결합이라 표현하는 것이 맞습니다.
그리고 디자인패턴을 적용할 지 말지는 글쎄요.. 저도 확답을 드리기가 어렵습니다. 디자인패턴에 숙련된 개발자라면 패턴화 시키는 코딩을 하거나, 소설처럼 내려쓰는 코딩을 하거나 크게 시간 차이가 발생하지 않습니다. 그렇지 않다면 많은 시간과 시행착오를 거치기 때문에 패턴화한 코딩을 하면 가뜩이나 짧은 프로젝트 일정에 맞추는 것도 힘들고.. 다른 사람에게 피해도 줄 수 있습니다.
하지만, 패턴화된 코드를 잘 모으면 이는 프레임워크란 이름으로 탄생됩니다.
디자인패턴을 통해 객체 설계를 면밀히 하여 진행된 좋은 결과로는 이클립스를 들 수 있을 것 같습니다.
이클립스는 패턴화로 설계된 객체들을 활용하고 새로 만들어 붙이면서 수많은 플러그인을 쏟아 내면서 그 활용폭을 계속 확장해 나가고 있습니다. 만일 패턴화하지 않았다면, 그런 일은 있을 수가 없었을 겁니다.
게임의 경우라면, 기반 프레임워크가 튼튼하면 확장팩을 수월하게 빠른 시일에 공급할 수 있을 수 있고..
정말 잘 만든 기반은 언리얼 엔진 처럼 별도의 부가가치를 창출하기도 합니다.
저의 경우도.. 다른 사람들이 요구사항에 따라 그냥 코딩만 할때.. 남들보다 더 일하면서 나름대로 패턴화하면서 프로그래밍을 시도한 결과.. 이들을 묶어 프레임워크로 제품을 만들어 내놓을 수 있었고, 여러 공공기관, 금융업계, 제조업계 쪽으로 납품을 하고.. 몇번 안되지만 강의도 뛰는 등... 아무튼 한단계 업그레이드 할 수 있는 계기가 되었습니다.
제 후배들한테 하는 이야기가 있는데, 일이 주어지면 최고 빠른 속도로 할 수 있는 방법으로 먼저 구현해서 납품하고, 그리고 나서 약간의 여유가 있다면 동일한 일을 내가 할 수 있는 최고 수준의 질로 다시 구현 해 보라라고 이야기 합니다.
그럴 시간이 없다고 불평하는 친구들이 있는데.. 그건 핑계라고 생각합니다. 술 한번 덜 마시면 되고... 게임 한판 덜하면 되고.. 웹서핑 한시간 덜 하면 됩니다.
-
-
-
오래된꿈읽기 2008/11/20 19:28
평종님 질문을 보고 저도 더 생각을 정리할 필요도 있고 해서 좋습니다. 이번에는 답변을 좀 생각해보고 달아야 할 필요도 있을 것 같고... 주말에 출사 여행을 가기로 해서... 답변이 좀 늦을 것 같네요... 월요일이 준비를 해서 화요일 쯤 답변 드릴 수 있을 것 같은데.. 괜찮으실지...?? 언제나 열심히 공부하시는 것 같아 보기 좋습니다. 그 열정이 부럽기도 하구요..
-






