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

Chapter 4 - Builder Pattern

파란실버라이트 2013. 1. 4. 15:27

 

 기본 아이디어

  • 객체를 생성하는 코드를 객체의 내부 표현으로 부터 분리시켜 동일한 생성 과정을 통해 여러 종류의 표현을 생성할 수 있도록
  • 비즈니스 객체(도메인 레벨의 추상화를 모델링하는 객체)를 세부구현 정보(비즈니스 객체를 어떻게 화면에 보일 것인가, 혹은 어떻게 데이터 베이스에 넣을 것인가 등)로 부터 분리시켜준다.
  • 빌터 패턴은 사용하면 도메인 레벨의 객체가 자신의 다양한 표현형을 생성할 수 있게 된다.
  • 비즈니스 객체는 패턴에서 Director의 역할을 맞고, Concrete Builder의 역할을 맡은 객체(builder interface 구현)표현을 생성

 

 

 

 

 

 //Table.Exporter : Builder

  public interface Exporter    //{=Table.Exporter}
 { 

public void startTable()   throws IOException;
  public void storeMetadata(
     String tableName,
     int width,
     int height,
     Iterator columnNames ) throws IOException;
  public void storeRow(Iterator data) throws IOException;
  public void endTable()     throws IOException;
 }

 

    //CSVExporter, JtableExporter : Contcrete Builder

public class JTableExporter implements Table.Exporter
{
     private String[] columnHeads;
     private Object[][] contents;
     private int   rowIndex = 0;

     public void startTable() throws IOException { rowIndex = 0; }

     public void storeMetadata( String tableName,
          int width,
          int height,
          Iterator columnNames ) throws IOException
 {
      contents = new Object[height][width];
      columnHeads = new String[width];

      int columnIndex = 0;
      while( columnNames.hasNext() )
           columnHeads[columnIndex++] = columnNames.next().toString();
 }

 public void storeRow( Iterator data ) throws IOException
 { 

int columnIndex = 0;
  while( data.hasNext() )
   contents[rowIndex][columnIndex++] = data.next();
  ++rowIndex;
 }

 public void endTable() throws IOException {/*nothing to do*/}

 /** Return the Concrete Product of this builder---a JTable
  *  initialized with the table data.
  */
 public JTable getJTable()
 { 

return new JTable( contents, columnHeads );
 }

 /** A unit test for the JTableExporter class
  * Run it with <em>java com.holub.database.JTableExporter\$Test</em>.
  */

 

public class CSVExporter implements Table.Exporter

private final Writer out;
 private    int  width;

 public CSVExporter( Writer out )
 { 

this.out = out;
 }

 public void storeMetadata( String tableName,
          int width,
          int height,
          Iterator columnNames ) throws IOException

 { 

this.width = width;
  out.write(tableName == null ? "<anonymous>" : tableName );
  out.write("\n");
  storeRow( columnNames ); // comma separated list of columns ids
 }

 

 public void storeRow( Iterator data ) throws IOException
 { 

int i = width;
  while( data.hasNext() )
  { Object datum = data.next();

   // Null columns are represented by an empty field
   // (two commas in a row). There's nothing to write
   // if the column data is null.
       if( datum != null ) 
    out.write( datum.toString() );

       if( --i > 0 )
            out.write(",\t");
      }
      out.write("\n");
 }

 public void startTable() throws IOException {/*nothing to do*/}
 public void endTable()   throws IOException {/*nothing to do*/}

}

 

 

  //ConcreteTable : Director

 

 public static class Test
{

public static void main( String[] args ) throws IOException
{

//Create Concrete Director
Table people = TableFactory.create( "people",
new String[]{ "First", "Last" } );


people.insert( new String[]{ "Allen", "Holub" } );
people.insert( new String[]{ "Ichabod", "Crane" } );
people.insert( new String[]{ "Rip", "VanWinkle" } );
people.insert( new String[]{ "Goldie", "Locks" } );

javax.swing.JFrame frame = new javax.swing.JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

 

//Create Concrete Builder

JTableExporter tableBuilder = new JTableExporter();

 

// Table의 Export method CSVExporter()대신 JTableExporter()를 넘긴다.

=> CSV표현 방법이 아닌 JTable를 표현(Export)한다.

=> Table이 아닌 구현된 Builder가 Export를 위임해서 일을 한다.

=> GetXML(), GetHTML, GetCSV() 와 같은 표현을 위한 함수를 만들지 않는다.

=> 구현된 Table Builder를 Director에 넘겨 동일한 일을 처리하게 하는 것이 Command Pattern과 비슷한 것 같다.       Builder pattern의 Point는 동일한 생성과정을 통해 하나의 타입의 다양한 표현을 하는 것으로 생각(2016.03.24)


people.export( tableBuilder );

 

//Concrete Table의 Export Methold 

//Concreate Table에서 Builder의 인터페이스를 구현이 실행됨

//JTableExporter는 JTable을 생성한 후 Jtable에 테이블의 데이타를 넣는다.

 public void export( Table.Exporter exporter ) throws IOException
 { exporter.startTable();
  exporter.storeMetadata( tableName,
                          columnNames.length,
                          rowSet.size(),
        new ArrayIterator(columnNames) );

  for( Iterator i = rowSet.iterator(); i.hasNext(); )
   exporter.storeRow( new ArrayIterator((Object[]) i.next()) );

  exporter.endTable();
  isDirty = false;
 }

 

 

 

frame.getContentPane().add(

   //데이터가 입련된 후에는 GetJtable()을 호출하여 JTableExporter로 부터 JTable을 가져온다.

   =>UI에서는 표현형에 상관없이 Builder를 통해서 도메인 모델에 접근하게 된다.

   =>다시 말해서 UI로직을 비즈니스 로직으로 부터 분리할 수 있도록 해준다.
    new JScrollPane( tableBuilder.getJTable() ) );


    frame.pack();
    frame.setVisible( true );
}
}
}

 

 

  JTableExporter 클래스에서 보았듯이 Builder는 아래 객체지향  UI문제를 해결해 준다.

 1. 만약 객체(people Table) 가 구현 상세를 노출시지 말아야 한다면 ?

   (이 때문에 getter와 Setter를 사용하면 안된다)  어떻게 사용자 인터페이스를 만들 것인가?

 2. 객체(people Table)가 자신을 다향한 방식으로 표현할 수 있어야 한다면? 

      객체는 자신의 Jcomponent 표현을 반환하는 메소드를 제공할 수 있을 것이다.

     하지만 웹을 위한 HTML표현을 제공해야 한다면? 데이타베이스와 통신하기 위해

     XML이나 SQL 표현이 필요하다면 ?

      도메인 모델 객체(people Table)에  GetXML(), getJcomponent(), getHTML()을 만들 것인가?

     위와 같은 방법을 사용하면 가능한 표현형마다 하나의 메소드를 제공해야 할 것이며, 적절한 방법이 아니다.

    

 정리
  • Builder 패턴은 객체를 표현을 '비즈니스' 로직과 분리해 주어 비즈니스 로직을 수정하지 않고도 새로운(혹은 변화된) 표현을 쉽게 추가할 수 있도록 해준다.
  • 객체가 자신의 표현을 getter를 이용하여 반환하게 되면 유지 보수상에 문제가 생길 수 있다. Builder 패턴이 이러한 문제를 해결해준다.
  • Builder 패턴을 사용하면 Director와 Builder가 강하게 결합된다, 또한 Builder의 인터페이스가 바꾸면 이를 구현한 모든 클래스(Concrete Builder)가 영향을 받게 된다. 그렇게 때문에 Builder 인터페이스 설계 시에는 세심한 주의가 필요하다.
  • 하지만 Builder 인터페이스가 변화하더라도 이는 Concrete Builder에만 영향을 미친다. 반면 Getter와 Setter를 사용했을 경우에는 프로그램 전반에 영향을 미치게 된다.