14.    The Command Pattern

Chain of Responsibility패턴이 클래스의 체인 안에서 요청 내용을 전달하는 반면, command패턴은 특정 객체에만 요청 내용을 전달한다.이것은 객체 안의 특정 처리 작업에 대한 요청 내용을 포함하며,해당 요청 내용을 알려진public인터페이스로 전달한다. Command패턴은 수행 작업의 내용을 몰라도 요청 내용을 작성할 수 있는 능력을 클라이언트에게 제공하며,클라이언트의 프로그램에는 어떠한 영향도 미치지 않고 개발자가 이런 수행 작업의 내용을 변경할 수 있게 한다.

 

구조

사용자 삽입 이미지

 

역할

  • Command :수행할operation을 위한 인터페이스를 선언한다.
  • ConcreteCommand : Receiver객체와action묶음을 정의한다.
  • Client : ConcreteCommand객체를 만들고receiver로 정한다.
  • Invoker : command에게request를 수행하도록 요청한다.
  • Receiver :처리할request에 대해 명령어들을 어떻게 수행해야 할지 알고 있다.어떠한 클래스든지Receiver로서 활동 가능하다.

 

의도

Request를 객체로 캡슐화시킴으로써 다른request들을 가진 클라이언트를 인자화시키거나, requestqueue하거나 대기시키며, undo가 가능한 명령을 지원한다.

 

적용시기

  • MenuItem객체가 하려는 일을 넘어서 수행하려는action에 의해 객체를 인자화시킬 때.프로그래머는procedural language에서의callback함수처럼 인자화시킬 수 있다. Commandcallback함수에 대한 객체지향적인 대안이다.
  • 다른 시간대에request를 구체화하거나queue하거나 수행하기 원할 때. Command객체는request와 독립적인lifetime을 가질 수 있다.만일requestreceiver가 공간 독립적인 방법으로(네트워크 등)주소를 표현할 수 있다면 프로그래머는request에 대한Command객체를 다른 프로세스에게 전달하여 처리할 수 있다.
  • undo기능을 지원하기 원할 때. CommandExecute operation은 해당Command의 효과를 되돌리기 위한state를 저장할 수 있다. CommandExecute수행의 효과를 되돌리기 위한Unexecute operation을 인터페이스로서 추가해야 한다.수행된commandhistory list에 저장된다. history list를 앞 뒤로 검색하면서UnexecuteExecute를 부름으로서 무제한의undo기능과redo기능을 지원할 수 있게 된다.
  • logging change를 지원하기 원할 때. logging change를 지원함으로써 시스템 충돌이 난 경우에 대해 해당command를 재시도 할 수 있다. Command객체에loadstore operation을 추가함으로써changelog를 유지할 수 있다. crash로부터 복구하는 것은 디스크로부터logged command를 읽어 들이고Execute operation을 재실행하는 것은 중요한 부분이다.
  • 기본명령어들을 기반으로 이용한 하이레벨의 명령들로 시스템을 조직할 때.그러함 조직은transaction을 지원하는 정보시스템에서 보편화된 방식이다. transaction은 데이터의 변화의 집합을 캡슐화한다. Command Patterntransaction을 디자인하는 하나의 방법을 제공한다. Command들은 공통된 인터페이스를 가지며,모든transaction를 같은 방법으로invoke할 수 있도록 한다. Command Pattern은 또한 새로운transaction들을 시스템에 확장시키기 쉽게 한다.

결론

l       Command패턴의 가장 큰 단점은 소규모 클래스를 증폭시킨다는 것이다.이 클래스들이 내부 클래스로 작동할 경우 메인 클래스에 달라붙거나 외부 클래스로 작동할 경우 프로그램 네임스페이스 부분에 달라붙는 문제를 갖고 있다.

l       Command패턴을 사용하는 도 다른 중요한 이유는 해당 패턴이undo기능에 대해 편리하게 이전 작업의 내용을 저장하고 실행할 수 있기 때문이다.각각의command객체는 연산 적업 및 메모리 용량이 지나치게 초과되지 않는다는 전제하에서undo명령어가 전달될 때 방금 전에 수행한 적업의 내용을 기억하여 복원할 수 있는 기능을 가지고 있다.최상위 수준에서는 단지 다음과 같은 두 가지command인터페이스를 다시 정의하면 된다.

public interface Command {

            public void Execute();

            public void undo();

}

그런 다음에는 각각의command객체가undo기능을 수행하기 위하여 이전 작업에 대한 기록을 저장하도록 설계해야 한다.중첩된 명령어 레코드를 이용하여 실행과 되돌리기 를 반복하다 보면,실행 속도가 느려질 수 있기 때문에undo기능의 구현 작업은 처음 생각했던 것보다 어려워지기도 한다.그리고 구체적으로 어떤 작업 내용을 되돌리기 해야 할지 파악할 수 있도록 각 명령어별로 실행 기록을 저장할 수 있는 충분한 공간이 필요하게 된다.

Unod명령어의 문제는 실질적으로 두 부분으로 분류된다.첫 번째는 실행된 명령문의 리스트를 저장하는 것이고,두 번째는 각각의 명령문별로 실행 내역을 리스트화 하는 것이다.

l        

 

 

예제소스

사용자 삽입 이미지

 

예제 소스

package command;

 

public interface Command {

   public abstract void execute();

}

 

package command;

 

import java.util.Stack;

import java.util.Iterator;

 

public class MacroCommand implements Command {

 

   private Stack commands = new Stack();

 

   public void execute() {

       Iterator it = commands.iterator();

       while (it.hasNext()) {

           ((Command)it.next()).execute();

       }

   }

 

   public void append(Command cmd) {

       if (cmd != this) {

           commands.push(cmd);

       }

   }

 

   public void undo() {

       if (!commands.empty()) {

           commands.pop();

       }

   }

 

   public void clear() {

       commands.clear();

   }

}

 

package drawer;

 

public interface Drawable {

   public abstract void draw(int x, int y);

}

 

package drawer;

 

import command.*;

 

import java.util.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

 

public class DrawCanvas extends Canvas implements Drawable {

 

   private Color color = Color.red;

 

   private int radius = 6;

 

   private MacroCommand history;

 

   public DrawCanvas(int width, int height, MacroCommand history) {

       setSize(width, height);

       setBackground(Color.white);

       this.history = history;

   }

 

   public void paint(Graphics g) {

       history.execute();

   }

 

   public void draw(int x, int y) {

       Graphics g = getGraphics();

       g.setColor(color);

       g.fillOval(x - radius, y - radius, radius * 2, radius * 2);

   }

}

 

package drawer;

 

import command.Command;

import java.awt.Point;

 

public class DrawCommand implements Command {

 

   protected Drawable drawable;

 

   private Point position;

 

   public DrawCommand(Drawable drawable, Point position) {

       this.drawable = drawable;

       this.position = position;

   }

 

   public void execute() {

       drawable.draw(position.x, position.y);

   }

}

 

import command.*;

import drawer.*;

 

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

 

public class mainClass extends JFrame

   implements ActionListener, MouseMotionListener, WindowListener {

   

   private MacroCommand history = new MacroCommand();

   

   private DrawCanvas canvas = new DrawCanvas(400, 400, history);

   

   private JButton clearButton = new JButton("clear");

   private JButton undoButton  = new JButton("undo");

 

   public mainClass(String title) {

       super(title);

 

       this.addWindowListener(this);

       canvas.addMouseMotionListener(this);

       clearButton.addActionListener(this);

       undoButton.addActionListener(this);

 

       Box buttonBox = new Box(BoxLayout.X_AXIS);

       buttonBox.add(clearButton);

       buttonBox.add(undoButton);

       Box mainBox = new Box(BoxLayout.Y_AXIS);

       mainBox.add(buttonBox);

       mainBox.add(canvas);

       getContentPane().add(mainBox);

 

       pack();

       setVisible(true);

   }

 

   public void actionPerformed(ActionEvent e) {

       if (e.getSource() == clearButton) {

           history.clear();

           canvas.repaint();

       }

       else if (e.getSource() == undoButton) {

           history.undo();

           canvas.repaint();

       }       

   }

 

   public void mouseMoved(MouseEvent e) {

   

   }

   

   public void mouseDragged(MouseEvent e) {

       Command cmd = new DrawCommand(canvas, e.getPoint());

       history.append(cmd);

       cmd.execute();

   }

 

   public void windowClosing(WindowEvent e) {

       System.exit(0);

   }

   

   public void windowActivated(WindowEvent e) {}

   public void windowClosed(WindowEvent e) {}

   public void windowDeactivated(WindowEvent e) {}

   public void windowDeiconified(WindowEvent e) {}

   public void windowIconified(WindowEvent e) {}

   public void windowOpened(WindowEvent e) {}

 

   public static void main(String[] args) {

       new mainClass("Command패턴Sample");

   }

}

 

관련패턴

l       Composite Pattern : MacroCommand를 구현하는데 이용될 수 있다.

l       Memento Pattern “ undo를 위한state를 유지할 수 있다.

l       Prototype Pattern :발생한 이벤트를 복제하고 싶은 경우에Prototype패턴이 사용되는 경우가 있다.

 


Posted by 영웅기삼
,