'Inforamtion Technology'에 해당되는 글 281건
- 2013.01.08 Chapter 4- FlyWeight 패턴
- 2013.01.08 Chapter 4- Decorator 패턴
- 2013.01.08 Chapter 4- Strategy 패턴
- 2013.01.08 Chapter 4- Command 패턴(Undo)
- 2013.01.07 Chapter 4 - Iterator Pattern
- 2013.01.04 Chapter 4 - Builder Pattern
- 2013.01.04 Chapter 4 - Bridge Pattern
- 2012.12.27 Chapter 3 - Facede Pattern
- 2012.12.27 Chapter 3 - Composite Pattern 1
- 2012.12.26 Chapter 3 - Visitor Pattern
기본 아이디어
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 패턴에서는 상속대신 합성을 사용하여 클래스 계층 구조를 단순화시킨다.
기본 아이디어
Strategy 패턴에서는 문제를 해결하여 여러 알고리즘이 존재할 경우 각 알고리즘을 별도의 클래스로 캡슐화하고, 이를 상호 교환 가능하도록 한다. 이때 각 알고리즘을 갭슐화한 클래스들(Concrete Strategy)은 알고리즘에 접속할 수 있는 인터페이스(Strategy)를 구현한다. 알고리즘을 사용하는 클라이언트(Context)는 Stategy 인터페이스를 통해 Concrete Strategy를 사용한다.
interface Selector
{
boolean approve( Cursor[] rows );
void modify( Cursor current );
public static class Adapter implements Selector
{
public boolean approve( Cursor[] tables )
{
return true;
}
public void modify( Cursor current )
{
throw new UnsupportedOperationException(
"Can't use a Selector.Adapter in an update");
}
}
public static final Selector ALL = new Selector.Adapter();
}
<Selector.Adapter를 확장>
people.delete ( new Selector.Adapter()
{
public boolean approve( Cursor[] tables )
{
return tables[0].column("lastName").equals("Flintstone");
}
}
- Table에 어떤 로우를 선택해야 하는지를 알고 있는 객체를 넘기는 것이다.
Selector가 바로 선택 전략을 캡슐화하고 있다.
- Delete() 메소드는 Strategy 객체의 approve() 메소드를 각 로우마다 호출
- Approve() 메소드는 해당로우가 삭제되어야 하는지에 대한 Boolean 값을 반환
<Selector 인터페이스를 직접 구현>
address.update
( new Selector()
{
public boolean approve( Cursor[] tables )
{
return tables[0].column("state").equals("AZ");
}
public void modify( Cursor current )
{
current.update("state", "AZ");
}
}
);
- 갱신 연산도 삭제와 동일한 방식으로 동작하지만 Selection의 approve() 메소드뿐 아니라 modify() 메소드도 오버라이딩해야
한다는 점이 다르다.
<Concete Table에서 Strategy 객체(Selector)를 사용 하는 코드>
public int update( Selector where )
{
Results currentRow = (Results)rows();
Cursor[] envelope = new Cursor[]{ currentRow };
int updated = 0;
while( currentRow.advance() )
{
if( where.approve(envelope) )
{
where.modify(currentRow);
++updated;
}
}
return updated;
}
//----------------------------------------------------------------------
public int delete( Selector where )
{
int deleted = 0;
Results currentRow = (Results) rows();
Cursor[] envelope = new Cursor[]{ currentRow };
while( currentRow.advance() )
{
if( where.approve( envelope) )
{
currentRow.delete();
++deleted;
}
}
return deleted;
}
- Update()와 Delete()가 하는 일은 순회 코드를 감추고 적절한 Strategy 객체(Selector)를 호출 하는 것이다.
- 이 코드는 또한 Passive Iterator의 변형형을 보여준다. 순회알고리즘은 Concrete Table안에 있으므로
Selector는 Table 객체들은 순회하는 Passive Iterator라고 할 수 있다.
기본 아이디어
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
//-----------------------------------------------------------------
- Begin() 메소드는 새로운 리스트를 스택에 넣는다.
- Regester() 메소드는 Undo 객체를 생성한 뒤 스택의 최상단에 있는 리스트의 끝에 추가한다.(스택이 비어 있으면 아무일도 일어나지 않는다. Begin 메소드가 호출되지 않았다면 스택은 비어있다.)
- 마지막으로 Commit()과 Rollback() Method 앞에서 설명한 것과 같이 리스트를 수정하거나 Undo 객체들을 실행시키게 된다.
=> 위 Code에 사용된 패턴은 C#의 Delegate, Event를 사용하는 것과 비슷하다. 등록된 모든 EventArg에게 Event 객체는 통보
정리
Concrete Command 객체는 Undo 연산을 위해 사용되는 메소드, 그리고 이에 필요한 상태 정보까지 정의 할 수 있다. (연산분 아니라 정보까지 캡슐화할 수 있다는 점이 C 언어의 함수 포인터와 다르다). 그러므로 command 패턴을 사용하여 스냅샷의 부작용인 비효율성을 제거하면서 연산으로 인한 부가 효과까지도 되롤릴 수 있다.
기본 아이디어
Iterator 패턴은 복합 객체 요소를 복합 객체의 구현을 노출시키지 않으면서 순회할 수 있는 방벙를 제공해준다.
Iterator의 실체화 코드
public final class ArrayIterator implements Iterator
{
private int position = 0;
private final Object[] items;
/**
* Create and <code>ArrayIterator</code>.
* @param items the array whose elements will be returned,
* in turn, by each {@link #next} call.
*/
public ArrayIterator(Object[] items){ this.items = items; }
public boolean hasNext()
{
return ( position < items.length );
}
public Object next()
{
if( position >= items.length )
throw new NoSuchElementException();
return items[ position++ ];
}
public void remove()
{
throw new UnsupportedOperationException(
"ArrayIterator.remove()");
}
/** Not part of the Iterator interface, returns the data
* set in array form. A clone of the wrapped array
* is actually returned, so modifying the returned array
* will not affect the iteration at all.
*/
public Object[] toArray()
{
return (Object[]) items.clone();
}
}
- Iterator 패턴을 원소를 특정 순서로 순회하도록 실체화할 수도 있다. 자료 구조의 내부 구현을 노출시키지 않으면서 순회할 수 있는 방법을 제공하는 것이 Iterator 패턴의 의도이면, 순회의 순서는 편의 따라 구현하면 된다.
- Iterator가 내부 자료 구조를 수정할 수도 있다. 하지만 이러한 경우 Iterator가 내부 잘 구조를 망치치 않도록 주의해야 한다.
단순한 이진 코드를 Passive와 Active Iterator를 구현한 코드
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import javax.print.DocFlavor.STRING;
public class Tree
{
private Node root = null;
private static class Node
{
public Node left, right;
public Object item;
public Node( Object item )
{
this.item = item;
}
}
// A Passive (internal) iterator
public interface Examiner
{
public void examine( Object currentNode );
}
public void traverse( Examiner client )
{
traverseInOrder( root, client );
}
private void traverseInOrder(Node current, Examiner client)
{
if( current == null)
return;
traverseInOrder(current.left, client);
client.examine (current.item );
traverseInOrder(current.right, client);
}
public void add( Object item )
{
if( root == null )
root = new Node( item );
else
insertItem( root, item );
}
//-------------------------------------------------------------
// An Active (external) iterator
//
public Iterator iterator()
{
return new Iterator() //자바는 interface의 익명 impemetation이 가능한다. C#는 불가
{
private Node current = root;
private LinkedList stack = new LinkedList();
public Object next()
{
while( current != null )
{
stack.addFirst( current );
current = current.left;
}
if(stack.size() != 0 )
{
current = (Node)( stack.removeFirst() );
Object toReturn=current.item;
current = current.right;
return toReturn;
}
throw new NoSuchElementException();
}
public boolean hasNext()
{
return !(current==null && stack.size()==0);
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
private void insertItem( Node current, Object item )
{
if(current.item.toString().compareTo(item.toString())>0)
{
if( current.left == null )
current.left = new Node(item);
else
insertItem( current.left, item );
}
else
{
if( current.right == null )
current.right = new Node(item);
else
insertItem( current.right, item );
}
}
public static void main( String[] args )
{
Tree t = new Tree();
t.add("D");
t.add("B");
t.add("F");
t.add("A");
t.add("C");
t.add("E");
t.add("G");
Iterator i = t.iterator();
//Active Iterator를 사용
while( i.hasNext() )
System.out.print( i.next().toString() );
System.out.println("");
//Passive Itrerator를 사용
t.traverse( new Examiner()
{
public void examine(Object o)
{
System.out.print( o.toString() );
}
}
);
System.out.println("");
}
}
트리를 방문하는 Active(External Iterator)를 구현한다고 생각해보자. Tree 클래스의 iterator() 메소드 는 트리의 각 노드를 중위(inorder) 순위로 방문하는 java.utiil.iterator를 반환한다. 이 코드는 매우 불분명하다. 알고리즘은 단순하지만 이해하기 어렵고 작성하기도 어렵다. (트리를 순회할 때 부모 노드를 기억하기 위해 스택을 사용)
하지만 Passive Iterator는 단순한 재귀 함수이며 작성하기 쉽다. TraverseInOrder 메소드는 Passive Iterator의 구현을 보여준다. 이 재귀 순회 알고리즘은 각 노드를 Examiner 객체에 차례로 전달할 뿐이다. 트리의 모든 노드를 Examiner 익명 인터페이스를 구현해서 다음과 같이 출력할 수 있다.
정리
- External Iterator 혹은 Active Iterator는 자신이 순회하는 자료 구조와 분리되어 있다.
- Passive Iterator는 혹은 Internal Iterator 객체를 생성하는 대신 자료 구조 내부에 순회 메커니즘을 구현한다.
- Passive Iterator는 혹은 Internal Iterator 자료 구조가 본질적으로 순회하기 어려운 구조일 때 유용하다.
기본 아이디어
- 객체를 생성하는 코드를 객체의 내부 표현으로 부터 분리시켜 동일한 생성 과정을 통해 여러 종류의 표현을 생성할 수 있도록
- 비즈니스 객체(도메인 레벨의 추상화를 모델링하는 객체)를 세부구현 정보(비즈니스 객체를 어떻게 화면에 보일 것인가, 혹은 어떻게 데이터 베이스에 넣을 것인가 등)로 부터 분리시켜준다.
- 빌터 패턴은 사용하면 도메인 레벨의 객체가 자신의 다양한 표현형을 생성할 수 있게 된다.
- 비즈니스 객체는 패턴에서 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를 사용했을 경우에는 프로그램 전반에 영향을 미치게 된다.
Gridge Pattern
- Bridge는 인터페이스를 통해 서브시스템을 격리 시킨다. 이는 한 서브시스템의 변화가 다른 서브시스템에 영향을 미치는 것을 막아준다.
- Facade 패턴의 주요 목적은 '단순화'이며 , 서브시스템을 격리시키지는 않는다.
1. Peer Class들은 java.awt.Toolkit라는 Abstract Factory를 사용하여 런타임에 생성된다.
2. Java.awt.Window 클래슨 Toolkit과 Peer에 대해서 알고 있다. Window를 생성할 때 window 객체를 적절한 peer를 생성하게 된다.
Window의 객체의 관점에서 프로그래밍 한다면 어떠한 Peer가 있는지에 대해서 전혀 알필요가 없다.
=> 결과적으로 브리짓의 다른 측면에 있는 것들(모든 Peer)은 심지어 런타임에도 바뀔 수 있으며, 이러한 변화는 브리지의 다른 쪽에는 영향을 미치지 않는다. 마찬가지로 , 피어 구현 중 어떤 것도 자신이 어떻게 사용되고 있는지에 대해 알지 못한다.
public final class Database
{
public Database() { }
public Database( File path, Table[] database ) throws IOException
{ useDatabase( path );
for( int i = 0; i < database.length; ++i )
tables.put( database[i].name(), database[i] );
}
public void createTable( String name, List columns )
{
String[] columnNames = new String[ columns.size() ];
int i = 0;
for( Iterator names = columns.iterator(); names.hasNext(); )
columnNames[i++] = (String) names.next();
Table newTable = TableFactory.create(name, columnNames);
tables.put( name, newTable );
}
public void dropTable( String name )
{ tables.remove( name ); // ignore the error if there is one.
File tableFile = new File(location,name);
if( tableFile.exists() )
tableFile.delete();
}
}
Database와 Table은 Bridge를 구성한다. Table 인터페이스는 다양한 Table 구현체를 은닉시켜 준다.
그리고 TableFactory는 다양한 Table 구현체 중 하나를 선택하여 사용할 수 있도록 해준다.
Database 관점에서 보면 데이타 저장 서브시스템(Table 측)의 변화가 Table 인터페이스를 통해서 은닉된다.
Facade 패턴은 서브시스템에 있는 인터페이스 집합에 대해 하나의 단순화된 통합 인터페이스를 제공하여
서브 시스템을 쉽게 사용하게 해준다.
//Faced인 MenuSite는 Client들에게 SubSystem에 다름과 같은 메소드를 제공한다.
public final class MenuSite
{
private static JFrame menuFrame = null;
private static JMenuBar menuBar = null;
public synchronized static void establish(JFrame container)
{
//구현된 코드
}
public static void addMenu( Object requester, String menuSpecifier )
{
createSubmenuByName( requester, menuSpecifier );
}
public static void removeMyMenus( Object requester )
{
//구현된 코드
}
public static void setEnable(Object requester, boolean enable)
{
//구현된 코드
}
public static JMenuItem getMyMenuItem(Object requester,
String menuSpecifier, String name)
{
//구현된 코드
}
public static void addLine( Object requester,
String toThisMenu,
String name,
ActionListener listener)
{
}
}
Clock이 Facede를 이용해서 MenuBar에 MenuItem을 추가하는 코드
public class Clock
{
private void createMenus()
{
// First set up a single listener that will handle all the
// menu-selection events except "Exit"
ActionListener modifier = //{=startSetup}
new ActionListener()
{ public void actionPerformed(ActionEvent e)
{
String name = ((JMenuItem)e.getSource()).getName();
char toDo = name.charAt(0);
if( toDo=='T' )
tick(); // single tick
else
startTicking( toDo=='A' ? 500: // agonizing
toDo=='S' ? 150: // slow
toDo=='M' ? 70 : // medium
toDo=='F' ? 30 : 0 ); // fast
}
};
// {=midSetup}
MenuSite.addLine(this,"Go","Halt", modifier);
MenuSite.addLine(this,"Go","Tick (Single Step)",modifier);
MenuSite.addLine(this,"Go","Agonizing", modifier);
MenuSite.addLine(this,"Go","Slow", modifier);
MenuSite.addLine(this,"Go","Medium", modifier);
MenuSite.addLine(this,"Go","Fast", modifier); // {=endSetup}
} //{=endCreateMenus}
}
Client ( Life, Clock, Neighborhood Class) 는 Facede(MenuSite)를 SubSystem에 객체지향적인 방식으로 접근할 수 있도록 한다.
- MenuSite는 자바 메뉴사이트(SubSystem)에 대한 Facede 이면, 메뉴 시스템을 단순화해 준다.
- 또한 공유된 메뉴바에 메뉴를 쉽게 추가 할 수 있도록 해준다.
Composite pattern 특징
- 같은 인터페이스를 구현하는 두 종류(Composite 와 Leaf)의 클래스 객체로 구성
- 공통 인터페이스는 Component의 담당
- 계층 구조에서 Leaf의 역할을 하는 객체 혹은 객체군이 존재 / 자식을 같지 않아 브랜치를 끝내는 노드 : Button CheckBox
- Composite 패턴을 이용하여 코드를 작성하면 계층 구조에 포함되어 있는 객체가 Leaf인지 composite인지 알 필요 없다
- => 둘 모두 하나의 인터페이스를 통해 접근 할 수 있다 => 코드가 단순해진다.
디렉토리 시스템 ; composite pattern의 예
Composite 패턴에서는 루트노드가 파생(자식) 클래스이며, 보통 Leaf 노드 클래스 아랫부분에 표시하게 된다.
- 다이어그램을 보면 Directory Class가 SimpleFile 밑에 표시되어 있다.
- 객체의 구조는 다이어그램과 반대이다. (컨테이너, 노드를 윗부분 / Leat 노드는 아랫부분에)
Composite 패턴을 사용하면 디렉토리 순회 시 하부 디렉토리(디렉토리, 파일)를 동일한 방식으로 취급할 수 있다.
위 그림의 주석 부분을 보면 객체에 단지 Print() 메시지를 보낼 뿐이다.
- 객체가 Simplefile이라면 단순히 파일 이름만 출력될 것이고
- 객체가 Directory라면 디렉토리가 포함하고 있는 내용을 출력할 것이다
public class Clock
{
public void tick()
{
publisher.publish
(
//Clock이 생성한 Distributor 구현은 Concrete Visitor
new Publisher.Distributor()
{
public void deliverTo( Object subscriber )
{
if( !menuIsActive() )
((Listener)subscriber).tick();
}
}
);
}
}
public class Publisher
{
public interface Distributor
{
void deliverTo( Object subscriber ); // the Visitor pattern's
} // "visit" method.
private volatile Node subscribers = null;
public void publish( Distributor deliveryAgent )
{
for(Node cursor = subscribers; cursor != null; cursor = cursor.next)
cursor.accept( deliveryAgent );
//deliveryAgent.deliverTo( subscriber ); // a "visitor"
}
synchronized public void subscribe( Object subscriber )
{
subscribers = new Node( subscriber, subscribers );
}
synchronized public void cancelSubscription( Object subscriber )
{
subscribers = subscribers.remove( subscriber );
}
}
private class Node
{
public final Object subscriber;
public final Node next;
private Node( Object subscriber, Node next )
{
this.subscriber = subscriber;
this.next = next;
}
public Node remove( Object target )
{
if( target == subscriber )
return next;
if( next == null ) // target is not in list
throw new java.util.NoSuchElementException
(target.toString());
return new Node(subscriber, next.remove(target));
}
{
deliveryAgent.deliverTo( subscriber ); // a "visitor"
}
}
- Clock : 클라이언트 객체
- Publisher는 Object Structure
- Distributor는 Visitor 인터페이스
- Node는 객체 구조 안에 포함되어 있는 Element
- Node.accept() : 'Accept) 요청
- deliverTo() : 'visit' 요청이다.
- Clock이 생성한 Distributor 구현은 Concrete Visitor
주로 호스트 객체는 자료 구조로 구성되어 있으며 Visitor는 이 자료 구조를 순회하며 호스트 객체를 방문/처리 한다.
Visitor 패턴 장점
- Visitor는 기존 객체를 변경하지 않으면서 새로운 연산을 추가할 수 있도록 해준다
- 이는 구현 시에 생각하지 못했던 혹은 알려지지 않았던 연산을 쉽게 구현할 수 있도록 해준다.
- 드물게 사용되는 연산을 외부에 구현할 수 있도록 해주기 때문에 클래스가 작아진다.
Visitor 패턴 주의 사항
- Visotor는 캡슐화를 위반 할수도 있으며, Visitor와 Element 사이의 결합도가 높다.
- 가능하면 Visitor가 Element의 Public 인터페이스 혹은 제한된 인터페이스를 접근하도록 하자
이 패턴 잘 이해가 안간다. 다시 공부해야 한다.^^