프로그래밍을 하다 보면 종종 사용자가 입력한 명령들을 기억해야 하는 경우가 있다. 매크로 커맨드를 만들어야 한다거나 undo / redo와 같이 사용자의 요청에 따라서 이미 수행한 명령을 취소하거나 다시 수행해야 할 필요가 있기 때문이다. 이럴 때 명령들을 객체화 하여 저장해 둠으로써 사용자에게 필요한 기능들을 제공해 주는 것이 Command 패턴이다.
Command 패턴의 이해는 우선 명령보다는 실제 다루고자 하는 객체로부터 시작된다. 우선 Shape 타입이 있고, 하위 타입인 Circle과 Rectangle이 다루려는 대상이다. Shape 타입은 draw() 메소드와 undraw() 메소드를 제공한다. 실제 하려고 하는 일은 이 두 메소드를 이용해서 도형을 그리거나 지우는 일이다.
Command 패턴에서 필요한 것은 Command를 객체화 하는 일이다. Shape을 다루기 위한 Command이므로 ShapeCommand라는 것을 만든다. 이 ShapeCommand는 위에서 정의한 Shape 타입에 대한 연산을 수행하는 객체이다. 예를 들어 ShapeCommand의 execute() 라는 메소드를 호출하면 Shape의 draw()가 호출되고, undo()라는 메소드를 호출하면 undraw()를 호출하는 식이다.
그리고 만약 Command들을 이용하여 사용자의 요청을 처리하고자 할 경우, 즉 사용자가 새로운 도형을 그리거나(execute) 이미 그린 객체를 지우거나(undo), 지웠던 객체를 다시 그리고자 할 때(redo) 명령 객체들을 저장해 두었다가 사용자의 요청에 따라 동작을 수행하는 객체가 필요하다. 아래 예제에서는 이를 CommandManager라는 이름으로 구현하였다.
Command 패턴의 클래스 다이어그램
우리가 다룰 대상 객체는 Circle과 Rectangle 객체이고, 이들을 공통 타입으로 묶기 위해서 Shape 인터페이스를 선언하였다. ShapeCommand는 Command 객체인데, 다양한 Command 객체가 구현될 수 있도록 ICommand 인터페이스를 선언하였다. ShapeCommand 객체는 대상이 되는 도형을 가지고 있고, 사용자의 명령에 따라 대상 객체를 조작한다. CommandManager 클래스는 사용자의 요청에 따라 생성된 Command 객체들을 저장하고 있다가 전체 실행(executeAll()), undo / redo와 같은 Command 객체 핸들링을 지원해주는 클래스이다.
Command 패턴의 구현
class CommandManager{
private List<ICommand> undo = new ArrayList<ICommand>();
private List<ICommand> redo = new ArrayList<ICommand>();
public void execute(ICommand command){
command.execute();
undo.add(command);
}
public void executeAll(){
for(ICommand command : undo){
command.execute();
}
}
public void undo(){
ICommand command = undo.get(undo.size() - 1);
command.undo();
undo.remove(command);
redo.add(command);
}
public void redo(){
ICommand command = redo.get(redo.size() - 1);
command.redo();
redo.remove(command);
undo.add(command);
}
}
interface ICommand{
public void execute();
public void undo();
public void redo();
}
class ShapeCommand implements ICommand{
Shape shape;
public void setShape(Shape shape){ this.shape = shape; }
public void execute(){ shape.draw(); }
public void undo(){ shape.undraw(); }
public void redo(){ execute(); }
}
interface Shape{
public void draw();
public void undraw();
}
class Circle implements Shape{
public void draw() {System.out.println("\tdraw Circle"); }
public void undraw() {System.out.println("\tundraw Circle"); }
}
class Rectangle implements Shape{
public void draw() {System.out.println("\tdraw Rectangle"); }
public void undraw() {System.out.println("\tundraw Rectangle"); }
}실행 방법
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int cmd;
CommandManager manager = new CommandManager();
do{
System.out.println("1.execute");
System.out.println("2.undo");
System.out.println("3.redo");
System.out.println("8.execute All");
cmd = scan.nextInt();
if(cmd == 1){
System.out.println("Which on?");
System.out.println("1.Circle");
System.out.println("2.Rectangle");
cmd = scan.nextInt();
if(cmd == 1){
ShapeCommand command = new ShapeCommand();
command.setShape(new Circle());
manager.execute(command);
}
else{
ShapeCommand command = new ShapeCommand();
command.setShape(new Rectangle());
manager.execute(command);
}
}
else if(cmd == 2){
manager.undo();
}
else if(cmd == 3){
manager.redo();
}
else if(cmd == 8){
manager.executeAll();
}
}while(cmd != 9);
}
main() 메소드를 통해서 Command 패턴을 테스트 하는 방식은 다음과 같다. 먼저 대상 객체를 생성하는 명령은 1번 exetue이고, undo는 2번, redo는 3번을 키보드로 입력하면 된다. 여태 실행된 명령어들을 보기 위해서는 8번을, 종료할 때는 9번을 입력한다. 1번을 입력했을 경우에는 어떤 도형을 생성할지를 선택해야 한다. 1번은 Circle을 2번은 Rectangle을 생성한다.
도형을 몇 개 생성하고 나서부터는 자연스럽게 undo와 redo를 수행해보고 결과를 execute all 명령을 통해 확인해 볼 수 있다.
CommandManager를 조금씩 확장시켜 나가면서 Macro(특정 시점 이후에 입력된 여러 명령어들을 모아서 연속으로 실행 시키는 것)를 만드는 것도 가능하다.
'5.디자인패턴' 카테고리의 다른 글
Mediator 패턴 (0) | 2016.09.18 |
---|---|
Facade 패턴 (0) | 2016.09.18 |
Flyweight 패턴 (0) | 2016.09.18 |
Chain Of Responsibility 패턴 (0) | 2016.09.17 |
Composite 패턴 (0) | 2016.09.17 |