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

Chapter 4- Decorator 패턴

파란실버라이트 2013. 1. 8. 10:41

기본 아이디어

Decorator 패턴은 '구현 상속(Extends)' 대신 '인터페이스 상속(Implements)/위임'  전략을 사용하여

깨지기 쉬운 기반 클래스 문제를 해결한다.

 

 

 

 

 public Table select( Selector where )
 { 

Table resultTable = new ConcreteTable( null,
          (String[]) columnNames.clone() );

 

      //result table을 Selector의 전략에 맞게 꺼내오는 부분 생략 
      return new UnmodifiableTable(resultTable);
 }


 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 public Table select(Selector where, String[] requestedColumns )
 { 

 

      Table resultTable = new ConcreteTable( null,
         (String[]) requestedColumns.clone() );

 //result table을 Selector의 전략에 맞게 꺼내오는 부분 생략


       return new UnmodifiableTable(resultTable);
 }

 

 - ConcreteTable을 생성하고, 조회한 후에 UnmodifiableTalbe을 사용하여 실제 결과 테이블을 래핑하여 반환

 

 

 public class UnmodifiableTable implements Table

       //UnmodifialbeTalbe의 Wrapped 에 Assign 한다.

 private Table wrapped;

 

 public UnmodifiableTable( Table wrapped )
 { 

       this.wrapped = wrapped;
 }

 /** Return an UnmodifieableTable that wraps a clone of the
  *  currently wrapped table. (A deep copy is used.)
  */


 public Object clone() throws CloneNotSupportedException
 { UnmodifiableTable copy = (UnmodifiableTable) super.clone();
  copy.wrapped = (Table)( wrapped.clone() );
  return copy;
 }

 

 public int  insert(String[] c, Object[] v   ){ illegal(); return 0;}
 public int  insert(Object[] v        ){ illegal(); return 0;}
 public int  insert(Collection c,Collection v ){ illegal(); return 0;}
 public int  insert(Collection v      ){ illegal(); return 0;}
 public int  update(Selector w     ){ illegal(); return 0;}
 public int  delete( Selector w     ){ illegal(); return 0;}

 public void begin  (    ){ illegal(); }
 public void commit  (boolean all){ illegal(); }
 public void rollback (boolean all){ illegal(); }

 

 private final void illegal()
 { 

     throw new UnsupportedOperationException();
 }

 public Table select(Selector w,String[] r,Table[] o)
 { 

     return wrapped.select( w, r, o );
 }
 public Table select(Selector where, String[] requestedColumns)
 { 

     return wrapped.select(where, requestedColumns );
 }
 public Table select(Selector where)
 { 

     return wrapped.select(where);
 }
 public Table select(Selector w,Collection r,Collection o)
 { 

     return wrapped.select( w, r, o );
 }
 public Table select(Selector w, Collection r)
 { 

     return wrapped.select(w, r);
 }
 public Cursor rows()
 { 

     return wrapped.rows();
 }
 public void  export(Table.Exporter exporter) throws IOException
 { 

     wrapped.export(exporter);
 }

 

 public String toString()   { return wrapped.toString(); }
 public String name()   { return wrapped.name();   }
 public void  rename(String s){    wrapped.rename(s);  }
 public boolean isDirty()  { return wrapped.isDirty();  }


 public Table extract(){ return wrapped; }

}

 

- Decorater(UnmodifieableTable)은 Table을 수정하는 메소드(insert, update, delete)가 호출 될 때는 예외

- 나머지 메소드들은 래핑된 Decorater에 작업을 위임한다.

 

  정리

   UnmodifiableTable은 ConcreteTable에 있는 여러 메소드의 행위를 변화시켜 Table을 효과적으로 수정 불가능하게 만드는 것이다.

   1. IsImmutable 플래그를 넣어 이 플래그를 검사하게 할 수도 있지만 이 해결방법은 너무 복잡하다.

   2. 클래스를 상속하여 테이블을 수정할 수 있는 메소드들이 예외를 던지도록 오버라이딩할 수도 있었지만

      이는 깨지기 쉬운 기반 클래스 문제를 야기할 수 있다.

      a. 기반 클래스에 테이블을 수정할 수 있는 메소드를 추가했는데 , 파생 클래스에서 이 메소드를 오버라이딩하는 것을 깜빡?

      b. 컴파일 타임 에러를 런타임 에러로 바꾸기 때문에 유지보수하기 어려운 코드를 만들 위험에 노출시키게 된다.

   3. 구현 상속으로 발생할 수 있는 문제를 인터페이스 상속/위임 전략을 통해 해결

      래핑된 객체가 구현하고 있는 인터페이스와 똑같은 인터페이스를 구현한 수에 필요한 경우 연산을 래핑되어 있는 객체에 위임

      (래핑되는 객체와 인터페이스를 공유하지 못한 다면 해당 객체를 기반 클래스로 해야 할 것이다)

    => 이 방법은 Extends를 사용하지 않고 행위를 변경시킬 수 있도록 해주기 때문에 깨지기 쉬운 기반 클래스 문제를 해결해준다.

 

 

 Java I/O 클래스 Decorator 패턴 - 압축되어 있는 바이트 스트림을 읽을 때 데코레이터 체인을 사용한 예

 

 

 

  1. 바이트를 읽는다.

  2. 버퍼링을 사용하여 읽기 작업을 보다 효율적으로 한다.

  3. 바이트 스트림의 압축을 푼다.

 

 자바는 위 문제를 세 개의 클래스를 이용하여 해결한다.

 1. FileInputStream에서 시작하며, 다음과 같이 인스턴스화 한다.

 

try
{

       InputStream in = new FileInputStream( "file.name" );
}
catch( IOException e )
{

       System.err.println( "Couldn't open file.name" );
       e.printStackTrace();
}

 

   2. 버퍼링을 데코레이션(혹은 래핑) 전략을 사용하여 추가한다.

      즉 InputStream 객체를 바이트를 버퍼링하는 또다른 InputStream 구현체를 사용하여 래핑한다.

       래퍼에 바이트를 요청하면 래퍼는 다시 래핑되어 있는 스트림 객체에 여러 바이트를 요청한 후 이를 반환 한다.

 

 

try
{

      InputStream in = new FileInputStream( "file.name" );
      in = new BufferedInputStream( in );
}
catch( IOException e )
{

      System.err.println( "Couldn't open file.name" );
      e.printStackTrace();

 

 3. 압축 해체는 또 다른 데코레이터를 사용하여 추가한다.

  try
{

      InputStream in = new FileInputStream( "file.name" );
      in = new BufferedInputStream( in );
      in = new GZipInputStream( in );
}
catch( IOException e )
{

      System.err.println( "Couldn't open file.name" );

      e.printStackTrace();
}

 

정리

 - 이러한 해결 방법은 유연하다. 원하는 기능을 위해 여러 가지 데코레이터를 조합하여 사용하면 되는 것인다.

 - 더 중요한 것은 각 데코레이터 자체는 단 하나의 문제를 해결하기 때문에 상대적으로 단순하다.

    => 결과적으로 데코레이터들은 작성 및 디버깅하기 쉬우며, 시스템에 다른 영향을 미치지 않으면서 수정이 용이

 - 여러 데코레이터들을 조합하면 여러 다양한 기능을 사용할 수 있다. 데코레이터 자체는 작지만, 합쳐지면 강력하다.

 - Decorator 패턴을 사용하면 향후 원하는 기능을 추가하기도 쉽다.

 - 주요 목적은 구현 상속의 대안을 제공하는 것이다. 기반 클래스의 행위를 수정하거나 기능을 추가하려 한다면  

    Decorator 패턴의   도입을 신중히 검토해 보자

-  Decorator들을 래핑하는 순서도 중요하다.

 

 

 

 Java Collection 클래스 decorator 패턴 사용의 예

 스레드 안정성을 추가하기 위해 핵심 컬렉션 클래스들을 어떻게 확장(Extends)했는지 보여준다.

 결과적으로 구체 클래스들의 숫자가 2배로 늘어났다.

 

 

 컬렉션에 불변 기능을 추가한다면 아마도 Lock() 메소드를 추가하여 컬렉션을 수정하는 메소드들이 예외를 던지게 할 수 있을 것이다.  하지만 구현 상속을 이용하여 이러한 수정을 했을 때 구체 클래스의 숫자는 다시 두 배가 된다.

 

 

 

  Collection 프레임워크 디자이너는 이와 같은 문제를 Decorator 패턴을 사용하여 해결하였다.

 

  Collection threadSafe =
                    Collections.synchronizedCollection( new LinkedList() );

 

 The Collections class looks something like this:

 

public class Collections // utility
{
//...
public static Collection synchronizedCollection(final Collection wrapped)
{

     return new SynchronizedCollection(wrapped);
}


private static class SynchronizedCollection implements Collection
{

     private Collection unsafe;

     public SynchronizedCollection( Collection unsafe )
{

     this.unsafe = unsafe;
}
public synchronized boolean add( Object toAdd )
{

    return unsafe.add( toAdd );
}
public synchronized boolean remove( Object toAdd )
{

     return unsafe.remove( toAdd );
}
// Implement synchronized version of all other Collection
// methods here ...
}
}

 

 

 다음과 같이 이중 래핑을 통해 동기화된 불편 컬렉션을 만들 수도 있다.

 

 Collection myList = new LinkedList();
//...
myList = Collections.synchronizedCollection
(

Collections.unmodifiableCollection
(

      myList
)
);

 

=> Decorator 패턴의 핵심은 총 2개의 클래스를 추가하는 것 만으로도 구현 상속 시 18개의 클래스를 필요로 했던 문제 해결

 

 

 정리

 - Decorator 패턴의 핵심은 객체에 새로운 능력을 추가하거나 객체의 행위를 변화시킬 수 있도록 해준다.

 - Decorator 패턴에서는 상속대신 합성을 사용하여 클래스 계층 구조를 단순화시킨다.