어떻게 오브젝트 생성 시 필요한 초기화 정보의 획득과 입력 작업을 오브젝트 생성 로직에서 분리할 수 있을까?
일반적으로 오브젝트 생성과 관련하여 개발자들의 관심사는 Constructor, Builder, Factory 패턴 등과 같은 것이다. 개발자들은 오브젝트 생성을 위해 상황에 맞는 적절한 생성 패턴을 사용하여 오브젝트 생성 알고리즘을 구현한다.
이와 같이 오브젝트 생성과 관련해서는 잘 정리된 생성 패턴들이 있지만, 오브젝트 생성 시 필요한 초기화 정보 획득과 등록 작업에 관련된 적절한 패턴에 대한 고민은 상대적으로 부족하다. 그러나 프로그램을 개발하다 보면 오브젝트 생성을 위한 생성 패턴뿐만 아니라 초기화 정보의 획득 및 입력 작업에 대해 일관되고 확장이 용이한 방법에 대해서도 자주 고민하게 된다.
프로그램에서 오브젝트를 생성하기 위해서는 오브젝트 초기화에 필요한 입력 정보를 오브젝트에 입력해야 한다. 생성되는 오브젝트가 수행하는 작업에 따라 초기화에 사용되는 입력 정보는 통신 관련 오브젝트라면 원격지 접속 주소, 데이터베이스 처리 관련 오브젝트라면 데이터베이스 접속 정보, 데이터 파일 관리 오브젝트라면 데이터 파일 디렉터리 위치와 파일명 등이 될 수 있다. 그리고 생성되는 오브젝트의 초기화 입력 정보 형식에 따라 문자형, 숫자형과 같은 단순 원시 자료형에서 복합 구조의 오브젝트 형식 등 입력되는 정보의 형식도 다양한 구조를 가질 수 있다. 또한 생성되는 오브젝트의 초기화 입력 정보의 추출 경로에 따라 초기화 입력 정보는 하드 코딩, 설정 파일, 설정 데이터베이스, 설정 리지스트리, 원격지 설정 서버 등 다양한 경로를 통해 획득할 수 있다.
이와 같이 오브젝트 생성을 위한 초기화 정보 획득 및 입력 작업은 오브젝트 작업 내용, 초기화 정보 입력 형식, 초기화 정보 추출 경로에 따라 다양한 조합이 만들어져 개발에 따른 복잡성이 증가하고 일관성 있는 구현을 방해하게 된다. 이렇게 오브젝트 생성과 관련된 초기화 정보 획득개발이 복잡하고 일관성 없는 개발로 빠져들기 쉬운 문제점을 해결하기 위해 오브젝트 생성 로직에서 설정자(Configurer) 패턴을 사용할 수 있다.
설정자(Configurer) 패턴은 전략 패턴을 응용한 것으로 오브젝트 생성 시 오브젝트 생성 로직과 초기화 정보 획득 및 등록 로직을 분리하여 초기화 정보 획득 및 등록 작업에 일관성을 부여하고 초기화 정보 획득 및 등록 작업 알고리즘을 필요에 따라 대체할 수 있는 구조를 제공한다.
아래는 Factory 패턴과 함께 사용된 설정자(Configurer) 패턴 클래스 다이어그램이다. Factory 패턴 구조는 Design Patterns 의 Factory 패턴 구조이고 Design Patterns에서 제시한 Factory 패턴에서 생성에 필요한 로직은 Factory에 구현하고 Factory는 초기화 정보 획득과 입력 작업을 Configurer에 위임한다. 즉 Factory에서 초기화 정보 획득 및 입력을 분리하여 Configurer가 처리하도록 Factory 패턴의 역할을 나누고 확장한 것이다.
설정자(Configurer) 패턴 구조
Product
- Factory Method가 생성하는 오브젝트 인터페이스
- Product 인터페이스 구현 클래스
- 생성 시 필요한 초기화 입력 Method 구현
- 오브젝트 생성 시 오브젝트 초기화 정보 입력 Method 인터페이스
- Configure 인터페이스 구현 클래스
- 오브젝트 생성관련 초기화 정보 추출 및 입력 처리, Setter Method구현 등
- configure Method에서 Product 구현 오브젝트에 초기화 정보 입력
- 오브젝트 생성 Method인 getObject 정의를 가진 Factory 인터페이스
- Factory 구현 클래스
- Product 오브젝트 생성 시 Configurer 오브젝트에 초기화 정보 추출 및 입력 처리 위임
- 오브젝트 생성관련 준비 작업 및 생성 오브젝트 반환
설정자(Configurer) 패턴은 Factory 패턴뿐만 아니라 Builder 패턴에서도 적용할 수 있는데 Factory패턴에서와 마찬가지로 Builder에서 초기화 정보 입력을 Configurer 오브젝트에 위임하는 방식으로 구현하면 된다.
적용 사례
이제부터 설정자 패턴 사용을 실제 예를 통해 살펴보자. 설명을 위해 사용한 Factory 패턴은 Spring Framework의 FactoryBean이다. 즉 Spring Framework 내에서 FactoyBean 사용 시 설정자 패턴을 적용하는 예를 설명할 것이다.
먼저 Java 프로그램에서 오브젝트가 어떻게 생성되는지 ektorp 란 라이브러리에서 HttpClient 오브젝트를 생성하는 프로그램을 보자. 아래는 소스를 보면 ektorp 라이브러리는 HttpClient 오브젝트 생성을 위해 Builder 패턴을 사용하고 있다.
HttpClient 오브젝트 생성 예
1 2 3 4 | HttpClient httpClient = new StdHttpClient.Builder() .host( "localhost" ) .port( 8080 ) .build(); |
HttpClientFactoryNoConfigurerBean.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.brm.pattern.configurer; import org.ektorp.http.HttpClient; import org.ektorp.http.StdHttpClient; import org.springframework.beans.factory.FactoryBean; public class HttpClientFactoryNoConfigurerBean implements FactoryBean<HttpClient> { private String host; private int port; public HttpClient getObject() throws Exception { return new StdHttpClient.Builder() .host(host) .port(port) .build(); } public Class<? extends HttpClient> getObjectType() { return StdHttpClient. class ; } public boolean isSingleton() { return true ; } public void setHost(String host) { this .host = host; } public void setPort( int port) { this .port = port; } } |
application.xml
1 2 3 4 5 6 7 8 | <? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" > < bean id = "myHttpClientOrg" class = "com.brm.pattern.configurer.HttpClientFactoryNoConfigurerBean" > < property name = "host" value = "localhost" /> < property name = "port" value = "8080" /> </ bean > </ beans > |
Spring Framework의 속성값 주입 기능을 통해 오브젝트 생성에 필요한 초기화 입력 정보를 HttpClientFactoryNoConfigurerBean 클래스 소스의 수정 없이도 수정할 수 있는 장점도 함께 생기게 되었다.
그러나 만약 어떤 애플리케이션 개발 구조상 위 두 속성 정보인 host와 port 정보를 데이터베이스로부터 입력 받는다면 어떻게 될까? 이 경우 속성값을 데이터베이스에 값으로 바로 추출하는 기능은 Spring Framework가 제공하지 않으므로 처음 임의의 Java 오브젝트를 Spring 프레임워크에서 사용하기 위해 부딪쳤던 문제와 유사한 문제에 다시 부딪치게 된다. 즉 Spring Framework의 속성값 주입 기능만으로는 부족하고 다른 방안을 찾아 소스를 수정해야만 된다. Spring Framework의 속성 값 주입 기능을 버리고 HtttpClient 오브젝트를 생성하기 위해 새로운 Spring FactoryBean 클래스 작성하여 host와 port 정보를 데이터베이스에서 읽도록 재 작성해야 한다. 그러나 Factory 로직을 분석하고 해당 초기화 입력 추출 로직을 대체하는 작업을 하는 개발자는 Factory 로직 내의 오브젝트 생성 로직과 초기화 입력 정보 획득 로직이 결합된 로직을 모두 분석해야 할 것이다. 여기서 예를 든 소스는 이 과정이 그렇게 복잡하지 않지만 일반적인 경우 항상 이런 경우를 기대할 수 없을 것이고 생성 과정이 복잡한 경우도 대해서도 고려해야 할 것이다. 이 과정이 복잡한 소스를 개발자가 분석 수정하는 경우 소스에 대한 분석과 수정에 따른 오류 영향도가 커지게 되어 개발 생산성은 저하될 것이다. 그러므로 오브젝트 생성 로직과 초기화 입력 정보 획득 및 등록 로직을 분리하여 할 수 있다면 그리고 개발자는 오브젝트 생성을 위해 단지 초기화 입력 정보 추출과 등록 로직만 수정할 수 있다면 개발 생산성은 높아 지게 될 것이다. 그리고 구조적으로 보면 Factory 개발 측면에서는 오브젝트 생성과 관련된 로직의 은익성을 확보하고 사용 오브젝트 사용 측면에서는 초기화 입력의 유연성을 제공받는다.
그럼 지금까지 Factory 패턴만 적용된 소스와 설정에 설정자 패턴을 추가해 보자. 먼저 Configurer 인터페이스를 정의한다.
Configurer.java
1 2 3 4 5 6 | package com.brm.pattern.configurer; public interface Configurer<T> { public void configure(T client) throws Exception; } |
다음으로 Configurer 인터페이스 구현 클래스를 작성한다.
HttpClientConfiguer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.brm.pattern.configurer; import org.ektorp.http.StdHttpClient.Builder; public class HttpClientConfiguer implements Configurer<Builder> { private String host; private int port; public void configure(Builder builder) throws Exception { builder.host(host).port(port); } public void setHost(String host) { this .host = host; } public void setPort( int port) { this .port = port; } } |
이제 HttpClient Factory 에서 초기 입력 정보 추출 로직과 등록하는 로직을 제거하고 Configurer 오브젝트를 호출하는 부분을 추가하면 아래와 같이 좀더 간결한 Factory 클래스가 된다.
HttpClientFactoryBean.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package com.brm.pattern.configurer; import org.ektorp.http.HttpClient; import org.ektorp.http.StdHttpClient; import org.ektorp.http.StdHttpClient.Builder; import org.springframework.beans.factory.FactoryBean; public class HttpClientFactoryBean implements FactoryBean<HttpClient> { private Configurer<Builder> configurer; public HttpClient getObject() throws Exception { Builder builder = new StdHttpClient.Builder(); configurer.configure(builder); return builder.build(); } public Class<? extends HttpClient> getObjectType() { return StdHttpClient. class ; } public boolean isSingleton() { return true ; } public void setConfigurer(Configurer<Builder> configurer) { this .configurer = configurer; } } |
application.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" > < bean id = "myHttpClientOrg" class = "com.brm.pattern.configurer.HttpClientFactoryNoConfigurerBean" > < property name = "host" value = "localhost" /> < property name = "port" value = "8080" /> </ bean > < bean id = "myHttpClient" class = "com.brm.pattern.configurer.HttpClientFactoryBean" > < property name = "configurer" ref = "configuerer" /> </ bean > < bean name = "configurer" class = "com.brm.pattern.configurer.HttpClientConfiguer" > < property name = "host" value = "localhost" /> < property name = "port" value = "8080" /> </ bean > </ beans > |
위에서 보면 새로운 Bean Configurer 정의가 추가 되었고 초기 입력 정보와 관련된 속성관리는 Configurer Bean 정의로 옮겨졌다.
이렇게 구조를 변경하여 오브젝트 생성 로직은 초기 입력 정보 추출 경로가 변경되더라도 Factory의 수정은 발생하지 않고 Configurer 구현 클래스만 상속 변경 또는 변경하여 추출 경로 변경에 대응할 수 있는 유연한 구조가 되었다. 이 예에서는 설명을 위해 간단한 구조만 언급하였지만 실제 오브젝트 생성에 필요한 초기 입력 정보는 생성 오브젝트가 복잡하고 다양한 환경에서 운영되기 위하여 적게는 하나의 입력에서 많게는 수 십개 이상의 초기 입력 정보를 가질 수 있다. 이런 경우 설정자 패턴이 더 빛나는 구조가 될 수 있을 것이다.
맺음말
필자가 설정자(Configurer) 패턴이라고 이름을 붙인 이 패턴은 Apache Camel Framework의 분석 과정에서 얻은 것이다. 한 예로 Camel의 Http Component 라이브러리에 설정자 패턴이 적용되어 있다. Camel Http Component는 설정자 패턴을 통해 Apache HttpClient 오브젝트의 수많은 초기 파라미터 입력을 생성 로직과 분리하여 입력할 수 있는 기능을 제공한다. 그리고 Spring Framework에서도 설정자 패턴과 유사한 로직을 볼 수 있는데, 필자의 분석으로는 설정자 패턴으로 명확한 개념으로 정리가 되지 않고 개발 소스 수준의 적용으로 보인다. 그리고 Camel Framework에서도 설정자 패턴을 단지 전략 패턴으로만 인식하고 있고 Camel Component에 일부는 적용되어 있지만 일관되게 적용되지 않고 있는 점으로 미루어 아직 패턴으로 인식하지 않는 것 같다. 그러나 Configurer를 하나의 패턴으로 인식하고 이런 측면에서 오브젝트 생성 프로그램을 개발 할 때 설정자 패턴을 적용하여 설정 사용에 일관성을 부여하면 개발자들에게 오브젝트 생성에 대한 초기 정보 등록의 생산성과 초기 정보 추출 확장성을 제공해 줄 수 있을 것이라 믿는다.
참고 사이트
1) ektorp : http://github.com/helun/Ektorp
2) Camel HTTP Component : http://camel.apache.org/http.html
댓글 없음:
댓글 쓰기