디자인패턴 연습 5
develop/java patterns 2008/06/18 00:00
연습 4의 목적을 요약하자면 Connection 이라는 제품군을 생성하기 위한 구조를 만드는 것이다.
제품군을 생성하기 위한 구조로는 Gof 의 패턴중 [develop/java patterns] - 디자인패턴 - AbstractFactory 가 있다.
AbstractFactory 를 적용한 그림을 본다.

AbstractFactory 로서 DaoContext 란 인터페이스를 두고 구현 클래서로 GenericDaoContext 를 만들었다.
GenericDaoContext 는 제품군인 Connection 을 생성하기 위해 여러 DaoConnectionProvider를 내부에서 관리해야 한다.
단순히 AbstractFactory 패턴만을 적용해서 여러개의 Connection 을 생성하려고 하니 문제가 있다.
DB연결할 Connection 의 종류가 몇 가지가 되는 지 우리는 모르기 때문이다. 상품의 가변성이 높다는 이야기다.
이를 극복하기 위해 생각한 방법은
GenericDaoContext 안에 Map 을 하나 두고, key 대 DaoConnectionProvider 를 넣어두고 getConnection(Strign key) 메소드로 접근하는 방법이다.
이 방법은 바로 [develop/java patterns] - 디자인패턴 - Flyweight 이다.
FlyWeight 패턴을 알기 앞서 반드시 [develop/java patterns] - 디자인패턴 - Singleton 을 이해해야 한다.
FlyWeight 패턴까지 적용한 그림을 본다.

앞서의 그림과 배열이 좀 달라졌을 뿐 크게 달라진 점은 없다.
DaoContext 의 getConnection 메소드에 id란 인수가 들어간 것 뿐이다.
먼저 DaoContext 인터페이스를 만들어본다.
TestMain 을 조금 변형해 테스트 해 본다.
다른 곳에서 사용할 때 편리하게 사용하기 위한 Decorator 를 제공하고자 GenericDaoContext 를 만들었다.
DaoContext 와 GenericDaoContext 를 이렇게 따로 분리해서 제공하는 것은 함부로 provider 를 바꿀 수 없게 하기 위함이다.
DaoContext 는 getter 메소드만 사용할 수 있고, setter 메소드는 없다.
그리고 GenericDaoContext 에 setter 메소드(put,remove...)가 존재하지만 protected 로 선언하여 확장 구현시에만 이용이 가능하다.
이는 Connection 을 제공하는 설정을 아무 곳에서나 사용할 수 있는 것을 일단 막으려고 한 것이다.
DaoConnectionProvider 가 실제 MultiThreadModel 로 동작하는데, 이를 잘 모르는 개발자가 아무 곳에서나 이를 갈아쳐서 발생하는 문제를 사전에 막고자 하기 위함이다.
doc04.zip
지금까지 해서 AbstractFactory, FactoryMethod, Flyweight 패턴을 사용하여 Connection 을 제공하는 구조를 만들었다. 유용하고 자주 쓰이는 형태이기 때문에 잘 익혀두면 언젠가 도움이 된다...
이제는 새로운 구조 설계를 하려고 한다.
그리고 이 구조의 결과는 DaoContext 에서 취급하는 또 하나의 제품군이 될 것이다.
먼저 다음 소스를 보자...
PreparedStatement 사용하는 코드에서 진한 부분은 가변적인 부분이고, 흐린 부분은 고정적인 부분이 된다.
결론부터 말하자면 가변적인 부분을 빼고 고정적인 부분을 자동으로 제공하는 구조를 만들고자 하는 것이다.
그렇다면 고정적인 부분을 처리해 주면서 가변적인 부분을 확장할 수 있는 추상 클래스를 생각할 수 있다.
그 추상 클래스를 확장한 객체 인스턴스를 DaoContext 에서 제공해 주도록 하는 것이 목표가 된다.
메소드 또는 함수란 놈을 살펴보면....
입력이 있고 처리가 있고 출력이 있다. 때론 예외도 발생한다.
우리가 하려는 것은 하나의 일관된 처리 구조인데, 입력이 모두 틀리고, 출력이 모두 틀리다.
그럼 먼저 정의해야 할 것은 입출력을 어떻게 표준화, 공통화 할 수 있느냐부터 출발해야 한다.
다양한 인수 조건을 어떻게 모든 처리에서 사용할 수 있을까를 생각해 본다.
다양한 출력 형식을 어떻게 모든 처리에서 이용할 수 있을까를 생각해 본다.
일단 PreparedStatement에 파라메터를 set 하는 부분의 타입은 int, long, float, double, String 정도만 생각하자.
이 정도만 해도 우리 업무의 80% 정도의 쿼리 처리를 수행할 수 있다고 단언한다.
Stream, Blob 등 나머지 20% 에 해당하는 것들은 나중에 생각하기로 한다.
이번 연습은 녹녹치 않다. 일단 목적에 충실하고 부가적인 건 나중에 생각하자...
설정 파일 핸들링은 별도의 기능일 뿐이니 연연하지는 말도록...
제품군을 생성하기 위한 구조로는 Gof 의 패턴중 [develop/java patterns] - 디자인패턴 - AbstractFactory 가 있다.
AbstractFactory 를 적용한 그림을 본다.
AbstractFactory 로서 DaoContext 란 인터페이스를 두고 구현 클래서로 GenericDaoContext 를 만들었다.
GenericDaoContext 는 제품군인 Connection 을 생성하기 위해 여러 DaoConnectionProvider를 내부에서 관리해야 한다.
단순히 AbstractFactory 패턴만을 적용해서 여러개의 Connection 을 생성하려고 하니 문제가 있다.
DB연결할 Connection 의 종류가 몇 가지가 되는 지 우리는 모르기 때문이다. 상품의 가변성이 높다는 이야기다.
이를 극복하기 위해 생각한 방법은
GenericDaoContext 안에 Map 을 하나 두고, key 대 DaoConnectionProvider 를 넣어두고 getConnection(Strign key) 메소드로 접근하는 방법이다.
이 방법은 바로 [develop/java patterns] - 디자인패턴 - Flyweight 이다.
FlyWeight 패턴을 알기 앞서 반드시 [develop/java patterns] - 디자인패턴 - Singleton 을 이해해야 한다.
FlyWeight 패턴까지 적용한 그림을 본다.
앞서의 그림과 배열이 좀 달라졌을 뿐 크게 달라진 점은 없다.
DaoContext 의 getConnection 메소드에 id란 인수가 들어간 것 뿐이다.
먼저 DaoContext 인터페이스를 만들어본다.
public interface DaoContext {
Connection getConnection(Object providerId) throws SQLException;
Enumeration providerIds();
boolean containsProviderId(Object providerId);
}
providerID 를 통해 Connection 을 반환하는 메소드와 함께 등록되어 있는 ProviderId를 확인할 수 있는 containsProviderId(), providerIds() 메소드를 제공하고자 했다.Connection getConnection(Object providerId) throws SQLException;
Enumeration providerIds();
boolean containsProviderId(Object providerId);
}
TestMain 을 조금 변형해 테스트 해 본다.
public class TestMain {
private DaoContext context; // 사용할 DaoContext
public TestMain(DaoContext context) {
this.context = context;
}
public void execute() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
conn = context.getConnection("1st");
stmt = conn.createStatement();
rset = stmt.executeQuery("SELECT 'OKAY' FROM DUAL");
if ( rset.next() ) {
System.out.println("TEST IS " + rset.getString(1) );
}
} finally {
try { rset.close(); } catch(Throwable ignore) {}
try { stmt.close(); } catch(Throwable ignore) {}
try { conn.close(); } catch(Throwable ignore) {}
}
}
public void execute2() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
conn = context.getConnection("2nd");
stmt = conn.createStatement();
rset = stmt.executeQuery("SELECT 'OKAY2' FROM DUAL");
if ( rset.next() ) {
System.out.println("TEST IS " + rset.getString(1) );
}
} finally {
try { conn.close(); } catch(Throwable ignore) {}
}
}
public static void main(String[] args) {
try {
// Log4j 로거 셋팅
Logger logger = Logger.getLogger(TestMain.class.getName());
PatternLayout layout = new PatternLayout("[%d{yyyyMMdd HHmmss SSS}]%m%n");
Appender appender = new ConsoleAppender(layout);
logger.addAppender(appender);
// Log4jListener 셋팅
Log4jListener log4jListener = new Log4jListener();
log4jListener.setLogger(logger);
log4jListener.setBeforeExecuteLevel((String)null);
log4jListener.setAfterExecuteLevel("info");
// 첫번째 provider 설정
JDBCConnectionProvider provider = new JDBCConnectionProvider();
provider.addStatementListener(log4jListener);
provider.setDriver("${DRIVER1}");
provider.setUrl("${URL1}");
provider.setUser("${USER1}");
provider.setPassword("${PASSWORD1}");
// 두번째 provider 설정
JDBCConnectionProvider provider2 = new JDBCConnectionProvider();
provider2.addStatementListener(StatementListener.ConsoleStatementListener);
provider2.setDriver("${DRIVER1}");
provider2.setUrl("${URL1}");
provider2.setUser("${USER1}");
provider2.setPassword("${PASSWORD1}");
// Provider 관리 Map 설정
final HashMap providers = new HashMap(); // final 인 이유는 innerClass 에서 접근하기 위해..
providers.put("1st",provider);
providers.put("2nd",provider2);
// DaoContext 만들기
DaoContext context = new DaoContext() {
public boolean containsProviderId(Object providerId) {
return providers.containsKey(providerId);
}
public Connection getConnection(Object providerId) throws SQLException {
DaoConnectionProvider provider = (DaoConnectionProvider)providers.get(providerId);
if ( provider == null )
throw new SQLException("DaoConnctionProvider " + providerId + " not found.");
return provider.getConnection();
}
public Enumeration<Object> providerIds() {
return Collections.enumeration(providers.keySet());
}
};
// 테스트 실행
TestMain testMain = new TestMain(context);
testMain.execute();
testMain.execute2();
} catch(Throwable t) {
t.printStackTrace();
}
}
}
TestMain 소스에서 인터페이스인 DaoContext 를 내부에서 구현해서 테스트를 했지만,private DaoContext context; // 사용할 DaoContext
public TestMain(DaoContext context) {
this.context = context;
}
public void execute() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
conn = context.getConnection("1st");
stmt = conn.createStatement();
rset = stmt.executeQuery("SELECT 'OKAY' FROM DUAL");
if ( rset.next() ) {
System.out.println("TEST IS " + rset.getString(1) );
}
} finally {
try { rset.close(); } catch(Throwable ignore) {}
try { stmt.close(); } catch(Throwable ignore) {}
try { conn.close(); } catch(Throwable ignore) {}
}
}
public void execute2() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
conn = context.getConnection("2nd");
stmt = conn.createStatement();
rset = stmt.executeQuery("SELECT 'OKAY2' FROM DUAL");
if ( rset.next() ) {
System.out.println("TEST IS " + rset.getString(1) );
}
} finally {
try { conn.close(); } catch(Throwable ignore) {}
}
}
public static void main(String[] args) {
try {
// Log4j 로거 셋팅
Logger logger = Logger.getLogger(TestMain.class.getName());
PatternLayout layout = new PatternLayout("[%d{yyyyMMdd HHmmss SSS}]%m%n");
Appender appender = new ConsoleAppender(layout);
logger.addAppender(appender);
// Log4jListener 셋팅
Log4jListener log4jListener = new Log4jListener();
log4jListener.setLogger(logger);
log4jListener.setBeforeExecuteLevel((String)null);
log4jListener.setAfterExecuteLevel("info");
// 첫번째 provider 설정
JDBCConnectionProvider provider = new JDBCConnectionProvider();
provider.addStatementListener(log4jListener);
provider.setDriver("${DRIVER1}");
provider.setUrl("${URL1}");
provider.setUser("${USER1}");
provider.setPassword("${PASSWORD1}");
// 두번째 provider 설정
JDBCConnectionProvider provider2 = new JDBCConnectionProvider();
provider2.addStatementListener(StatementListener.ConsoleStatementListener);
provider2.setDriver("${DRIVER1}");
provider2.setUrl("${URL1}");
provider2.setUser("${USER1}");
provider2.setPassword("${PASSWORD1}");
// Provider 관리 Map 설정
final HashMap providers = new HashMap(); // final 인 이유는 innerClass 에서 접근하기 위해..
providers.put("1st",provider);
providers.put("2nd",provider2);
// DaoContext 만들기
DaoContext context = new DaoContext() {
public boolean containsProviderId(Object providerId) {
return providers.containsKey(providerId);
}
public Connection getConnection(Object providerId) throws SQLException {
DaoConnectionProvider provider = (DaoConnectionProvider)providers.get(providerId);
if ( provider == null )
throw new SQLException("DaoConnctionProvider " + providerId + " not found.");
return provider.getConnection();
}
public Enumeration<Object> providerIds() {
return Collections.enumeration(providers.keySet());
}
};
// 테스트 실행
TestMain testMain = new TestMain(context);
testMain.execute();
testMain.execute2();
} catch(Throwable t) {
t.printStackTrace();
}
}
}
다른 곳에서 사용할 때 편리하게 사용하기 위한 Decorator 를 제공하고자 GenericDaoContext 를 만들었다.
public class GenericDaoContext implements DaoContext {
private HashMap providers;
protected GenericDaoContext() {
this.providers = new HashMap();
}
@Override
public Connection getConnection(Object providerId) throws SQLException {
DaoConnectionProvider provider = (DaoConnectionProvider)this.providers.get(providerId);
if ( provider == null )
throw new SQLException("DaoConnctionProvider " + providerId + " not found.");
return provider.getConnection();
}
@Override
public Enumeration<Object> providerIds() {
return Collections.enumeration(this.providers.keySet());
}
@Override
public boolean containsProviderId(Object id) {
return this.providers.containsKey(id);
}
// provider 등록
protected void putDaoConnectionProvider(Object providerId, DaoConnectionProvider provider) {
this.providers.put(providerId,provider);
}
// provider 제거
protected void removeDaoConnectionProvider(Object providerId) {
this.providers.remove(providerId);
}
}
DaoContext 에는 없던 put, remove 메소드를 추가로 구성했다.private HashMap providers;
protected GenericDaoContext() {
this.providers = new HashMap();
}
@Override
public Connection getConnection(Object providerId) throws SQLException {
DaoConnectionProvider provider = (DaoConnectionProvider)this.providers.get(providerId);
if ( provider == null )
throw new SQLException("DaoConnctionProvider " + providerId + " not found.");
return provider.getConnection();
}
@Override
public Enumeration<Object> providerIds() {
return Collections.enumeration(this.providers.keySet());
}
@Override
public boolean containsProviderId(Object id) {
return this.providers.containsKey(id);
}
// provider 등록
protected void putDaoConnectionProvider(Object providerId, DaoConnectionProvider provider) {
this.providers.put(providerId,provider);
}
// provider 제거
protected void removeDaoConnectionProvider(Object providerId) {
this.providers.remove(providerId);
}
}
DaoContext 와 GenericDaoContext 를 이렇게 따로 분리해서 제공하는 것은 함부로 provider 를 바꿀 수 없게 하기 위함이다.
DaoContext 는 getter 메소드만 사용할 수 있고, setter 메소드는 없다.
그리고 GenericDaoContext 에 setter 메소드(put,remove...)가 존재하지만 protected 로 선언하여 확장 구현시에만 이용이 가능하다.
이는 Connection 을 제공하는 설정을 아무 곳에서나 사용할 수 있는 것을 일단 막으려고 한 것이다.
DaoConnectionProvider 가 실제 MultiThreadModel 로 동작하는데, 이를 잘 모르는 개발자가 아무 곳에서나 이를 갈아쳐서 발생하는 문제를 사전에 막고자 하기 위함이다.
doc04.zipjavadoc
지금까지 해서 AbstractFactory, FactoryMethod, Flyweight 패턴을 사용하여 Connection 을 제공하는 구조를 만들었다. 유용하고 자주 쓰이는 형태이기 때문에 잘 익혀두면 언젠가 도움이 된다...
이제는 새로운 구조 설계를 하려고 한다.
그리고 이 구조의 결과는 DaoContext 에서 취급하는 또 하나의 제품군이 될 것이다.
먼저 다음 소스를 보자...
public MyEtt getMyEtt(String field1, int field2) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rset = null;
MyEtt ett = null;
try {
conn = daoContext.getConnection("1st");
stmt = conn.prepareStatement("SELECT * FROM tables WHERE filed1=? AND filed2=?");
stmt.setString(1,field1);
stmt.setInt(2,field2);
rset = stmt.executeQuery();
if ( rset.next() ) {
ett = new MyEtt();
ett.setFiled1( rset.getString("field1") );
ett.setFiled2( rset.getInt("field2") );
ett.setFiled3( rset.getString("field3") );
}
return ett;
} catch(Throwable t) {
} finally {
try { rset.close(): } catch(Throwable ignore) {}
try { stmt.close(): } catch(Throwable ignore) {}
try { conn.close(): } catch(Throwable ignore) {}
}
}
위의 소스에서 진한 부분이 무엇을 의미하는지 생각해 보자...Connection conn = null;
PreparedStatement stmt = null;
ResultSet rset = null;
MyEtt ett = null;
try {
conn = daoContext.getConnection("1st");
stmt = conn.prepareStatement("SELECT * FROM tables WHERE filed1=? AND filed2=?");
stmt.setString(1,field1);
stmt.setInt(2,field2);
rset = stmt.executeQuery();
if ( rset.next() ) {
ett = new MyEtt();
ett.setFiled1( rset.getString("field1") );
ett.setFiled2( rset.getInt("field2") );
ett.setFiled3( rset.getString("field3") );
}
return ett;
} catch(Throwable t) {
} finally {
try { rset.close(): } catch(Throwable ignore) {}
try { stmt.close(): } catch(Throwable ignore) {}
try { conn.close(): } catch(Throwable ignore) {}
}
}
PreparedStatement 사용하는 코드에서 진한 부분은 가변적인 부분이고, 흐린 부분은 고정적인 부분이 된다.
결론부터 말하자면 가변적인 부분을 빼고 고정적인 부분을 자동으로 제공하는 구조를 만들고자 하는 것이다.
그렇다면 고정적인 부분을 처리해 주면서 가변적인 부분을 확장할 수 있는 추상 클래스를 생각할 수 있다.
그 추상 클래스를 확장한 객체 인스턴스를 DaoContext 에서 제공해 주도록 하는 것이 목표가 된다.
메소드 또는 함수란 놈을 살펴보면....
입력이 있고 처리가 있고 출력이 있다. 때론 예외도 발생한다.
우리가 하려는 것은 하나의 일관된 처리 구조인데, 입력이 모두 틀리고, 출력이 모두 틀리다.
그럼 먼저 정의해야 할 것은 입출력을 어떻게 표준화, 공통화 할 수 있느냐부터 출발해야 한다.
다양한 인수 조건을 어떻게 모든 처리에서 사용할 수 있을까를 생각해 본다.
다양한 출력 형식을 어떻게 모든 처리에서 이용할 수 있을까를 생각해 본다.
일단 PreparedStatement에 파라메터를 set 하는 부분의 타입은 int, long, float, double, String 정도만 생각하자.
이 정도만 해도 우리 업무의 80% 정도의 쿼리 처리를 수행할 수 있다고 단언한다.
Stream, Blob 등 나머지 20% 에 해당하는 것들은 나중에 생각하기로 한다.
이번 연습은 녹녹치 않다. 일단 목적에 충실하고 부가적인 건 나중에 생각하자...
설정 파일 핸들링은 별도의 기능일 뿐이니 연연하지는 말도록...
- 연습5.
- 위에서 언급한 PreparedStatement 처리를 도와주는 모델을 디자인하라.
- 이 모델을 DaoContext 에 새로운 상품군으로서 구성한다.
- TestMain 의 설정 영역, 공통 영역, 개별 영역을 잘 나누어 테스트하라.
- 힌트
- 지금까지의 패턴들을 다시 한번 사용한다.
- 우리는 어떤 본을 뜰 수 있는 소스만 있다면 쉽게 구현하곤 한다.
- 스타크래프트에서 우리는 유닛 혹은 건물에 명령을 내린다.
- Watrix DBTAO, Spring DAO, iBatis 같은 프레임워크들은 이 이슈를 해결하기 위해 노력했다.
- 더 생각해 볼 것
- executeQuery 를 통해 얻는 것은 두가지 형태가 될 수 있다.
- executeUpdate 의 처리도 빼 놓을 수는 없다.
- Exception 처리는 아주 중요한 이슈가 될 수 있다.
'develop > java patterns' 카테고리의 다른 글
| 디자인패턴 - TemplateMethod (0) | 2008/06/24 |
|---|---|
| 디자인패턴 - Flyweight (3) | 2008/06/18 |
| 디자인패턴 연습 5 (0) | 2008/06/18 |
| 디자인패턴 - Flyweight (3) | 2008/06/18 |
| 디자인패턴 - Singleton (1) | 2008/06/17 |
| 디자인패턴 - AbstractFactory (0) | 2008/06/16 |





