2011년 5월 23일 월요일

The Command Pattern

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

결론
  • Command 패턴의 가장 큰 단점은 소규모 클래스를 증폭시킨다는 것이다. 이 클래스들이 내부 클래스로 작동할 경우 메인 클래스에 달라붙거나 외부 클래스로 작동할 경우 프로그램 네임스페이스 부분에 달라붙는 문제를 갖고 있다.
  • Command 패턴을 사용하는 도 다른 중요한 이유는 해당 패턴이 undo 기능에 대해 편리하게 이전 작업의 내용을 저장하고 실행할 수 있기 때문이다. 각각의 command 객체는 연산 적업 및 메모리 용량이 지나치게 초과되지 않는다는 전제하에서 undo 명령어가 전달될 때 방금 전에 수행한 적업의 내용을 기억하여 복원할 수 있는 기능을 가지고 있다. 최상위 수준에서는 단지 다음과 같은 두 가지 command 인터페이스를 다시 정의하면 된다.
    public interface Command {
    public void Execute();
    public void undo();
    }
    • 그런 다음에는 각각의 command 객체가 undo 기능을 수행하기 위하여 이전 작업에 대한 기록을 저장하도록 설계해야 한다. 중첩된 명령어 레코드를 이용하여 실행과 되돌리기 를 반복하다 보면, 실행 속도가 느려질 수 있기 때문에 undo 기능의 구현 작업은 처음 생각했던 것보다 어려워지기도 한다. 그리고 구체적으로 어떤 작업 내용을 되돌리기 해야 할지 파악할 수 있도록 각 명령어별로 실행 기록을 저장할 수 있는 충분한 공간이 필요하게 된다.
    • Unod 명령어의 문제는 실질적으로 두 부분으로 분류된다. 첫 번째는 실행된 명령문의 리스트를 저장하는 것이고, 두 번째는 각각의 명령문별로 실행 내역을 리스트화 하는 것이다.


    예제소스


    예제 소스
    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");
    }
    }


    관련패턴
    • Composite Pattern : MacroCommand를 구현하는데 이용될 수 있다.
    • Memento Pattern “ undo를 위한 state를 유지할 수 있다.
    • Prototype Pattern : 발생한 이벤트를 복제하고 싶은 경우에 Prototype 패턴이 사용되는 경우가 있다.

댓글 없음:

댓글 쓰기

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...