DESIGN PATTERN/실용주의 디자인패턴

Chapter 4- Command 패턴(Undo)

파란실버라이트 2013. 1. 8. 09:46

 기본 아이디어

Command Pattern은 요청 혹은 작업의 단위를 캡슐화하여 연산을 이용(호출)하는 객체(예제의 controler, Invoker)와 연산을

실제로 수행하는 객체(위의 Background, Conrete Command) 객체 간의 결합도를 줄여준다.

 

//Runnable interface를 구현한 BackgrounTask Concrete Command 객체 

Runnable backgroundTask = new Runnable()
{
  public void run()
  {
    System.out.println("Hello World");
  }
};

and then pass it to a Thread object:

//Concrete command 객체의 Run을 실행시키는 Invoker(Controler) 객체

Thread controller = new Thread( backgroundTask);

 

You then start up the thread like this:
controller.start();

 

and the controller asks the Command object to do whatever it does by calling run().


 

마지막 수행문이 Command Pattern과 Command 객체를 사용하는 다른 패턴(Stratage pattern 등)을 구분짓게 해주는 핵심이다.

Command 패턴에서는 Invoker(Tread 객체)Concrete Command 객체(BackgroundTask)가 무엇을 하는지에 대해 알지 못한다.

 

 


 

 

- Undo System의 구현-

 스냅샷을 이용한  Undo 구현은 비효율적일 뿐만 아니라 연산의 부가 효과를 되돌릴 수 없다. 그러므로 Memento 패턴을 이용하여 Undo를 구현하는 것은 대부분의 경우 현실적이지 않다. Undo는 프로그램을 원래 상태로 되돌리는 것뿐 아니라 데이타베이스의 상태도 이전 상태로 되돌려 놓아야 한다.스냅샷은 프로그램의 상태만을 되돌린다. Command Pattern을 응용해서 사용하면 스냅샷으로 인한 부작용을 없애면서도 Undo 시스템을 멋지게 구현할 수 있다.

 

 

 

  //@cursor-end
 //----------------------------------------------------------------------
 // Undo subsystem.
 //

//Abtstract Command
 private interface Undo
 { 

void execute();
}

 //Concrete Command 객체들
 private class UndoInsert implements Undo
 { 

      private final Object[] insertedRow;
      public UndoInsert( Object[] insertedRow )
  { 

       this.insertedRow = insertedRow;
  }
  public void execute()
  { 

       rowSet.remove( insertedRow );
  }
 }


 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 private class UndoDelete implements Undo
 { 

       private final Object[] deletedRow;
       public UndoDelete( Object[] deletedRow )
  { 

        this.deletedRow = deletedRow;
  }
      public void execute()
  {

        rowSet.add( deletedRow );
  }
 }


 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 private class UndoUpdate implements Undo
 {
  private Object[] row;
  private int cell;
  private Object oldContents;

  public UndoUpdate( Object[] row, int cell, Object oldContents )
  {

   this.row    = row;
   this.cell   = cell;
   this.oldContents = oldContents;
  }

  public void execute()
  { 

         row[ cell ] = oldContents;
  }
 }


 

 

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public void begin()
{

       transactionStack.addLast( new LinkedList() );
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private void register( Undo op )
{

        ((LinkedList) transactionStack.getLast()).addLast( op );
}
private void registerUpdate(Object[] row, int cell, Object oldContents)
{

     if( !transactionStack.isEmpty() )
        register( new UndoUpdate(row, cell, oldContents) );
}
private void registerDelete( Object[] oldRow )
{

      if( !transactionStack.isEmpty() )
         register( new UndoDelete(oldRow) );
}
private void registerInsert( Object[] newRow )
{

       if( !transactionStack.isEmpty() )
          register( new UndoInsert(newRow) );
}


 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 public void commit( boolean all ) throws IllegalStateException
 { 

if( transactionStack.isEmpty() )
   throw new IllegalStateException("No BEGIN for COMMIT");
  do
  { LinkedList currentLevel =
       (LinkedList) transactionStack.removeLast();

       if( !transactionStack.isEmpty() )
        ((LinkedList)transactionStack.getLast())
            .addAll(currentLevel);

      } while( all && !transactionStack.isEmpty() );
 }

 


 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 public void rollback( boolean all ) throws IllegalStateException
 { 

if( transactionStack.isEmpty() )
   throw new IllegalStateException("No BEGIN for ROLLBACK");
  do
  { LinkedList currentLevel =
       (LinkedList) transactionStack.removeLast();

       while( !currentLevel.isEmpty() )
        ((Undo) currentLevel.removeLast()).execute();

      } while( all && !transactionStack.isEmpty() );
 }
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 //@undo-end
 //-----------------------------------------------------------------

  1.  Begin() 메소드는 새로운 리스트를 스택에 넣는다.
  2.  Regester() 메소드는 Undo 객체를 생성한 뒤 스택의 최상단에 있는 리스트의 끝에 추가한다.(스택이 비어 있으면 아무일도 일어나지 않는다. Begin 메소드가 호출되지 않았다면 스택은 비어있다.)
  3. 마지막으로 Commit()과 Rollback() Method 앞에서 설명한 것과 같이 리스트를 수정하거나 Undo 객체들을 실행시키게 된다.                                  

 => 위 Code에 사용된 패턴은 C#의 Delegate, Event를 사용하는 것과 비슷하다. 등록된 모든 EventArg에게 Event 객체는 통보

http://paransilverlight.tistory.com/191

 

 

 정리

Concrete Command 객체는 Undo 연산을 위해 사용되는 메소드, 그리고 이에 필요한 상태 정보까지 정의 할 수 있다. (연산분 아니라 정보까지 캡슐화할 수 있다는 점이 C 언어의 함수 포인터와 다르다). 그러므로 command 패턴을 사용하여 스냅샷의 부작용인 비효율성을 제거하면서 연산으로 인한 부가 효과까지도 되롤릴 수 있다.