기본 아이디어
- 객체를 생성하는 코드를 객체의 내부 표현으로 부터 분리시켜 동일한 생성 과정을 통해 여러 종류의 표현을 생성할 수 있도록
- 비즈니스 객체(도메인 레벨의 추상화를 모델링하는 객체)를 세부구현 정보(비즈니스 객체를 어떻게 화면에 보일 것인가, 혹은 어떻게 데이터 베이스에 넣을 것인가 등)로 부터 분리시켜준다.
- 빌터 패턴은 사용하면 도메인 레벨의 객체가 자신의 다양한 표현형을 생성할 수 있게 된다.
- 비즈니스 객체는 패턴에서 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를 사용했을 경우에는 프로그램 전반에 영향을 미치게 된다.