반응형
처음에 Inversion of Control(IoC : 제어의 반전) 패턴은 Dependency Injection 패턴이라고도 불리며 최근의 J2EE 커뮤니티에서는 자주 이용되고 있습니다.Spring, PicoContainer, HiveMind와 같이 IoC 패턴을 사용해서 경량 J2EE 컨테이너를 개발하고 있는 오픈 소스 프로젝트도 몇 개 있습니다.하지만 IoC는 새로운 개념이 아닙니다.이 패턴은 수년전부터 이용되고 있습니다.IoC 패턴에서는 인터페이스, 상속, 폴리모피즘과 같은 객체 지향 설계의 원칙 및 특징을 사용하여 소프트웨어 컴포넌트의 결합을 약화시키고 컴포넌트의 재이용과 테스트가 용이하도록 하는 소프트웨어 설계를 실현합니다.본 글에서는 IoC 패턴의 개요를 설명하고 오픈 소스의 IoC 프레임워크를 전혀 구현하지 않고 IoC 패턴을 소프트웨어 설계에 도입하는 방법을 소개하겠습니다.IoC 디자인 패턴 클래스 A와 클래스 B 사이에 클래스 A가 클래스 B의 서비스를 사용한다고 하는 관계가 있다고 하겠습니다.이 관계를 실현하는 일반적인 방법은 클래스 A의 내부에서 클래스 B를 인스턴스화하는 것입니다.이 방법으로도 동작 상으로는 문제가 없지만, 클래스 간의 연결이 강해져 버립니다.클래스 A를 수정하지 않고 클래스 B를 쉽게 변경할 수 없기 때문입니다.클래스 간 연결을 약화시키려면 클래스 B의 인스턴스(오브젝트 ′b′)를 클래스 A의 인스턴스(오브젝트 ′a′)에 주입할 Configurator를 준비합니다.나중에 클래스 B 구현을 변경하고 싶은 경우 Configurator 객체를 변경하기만 하면 됩니다.즉 오브젝트 ′a′ 가 오브젝트 ′b′ 참조를 취득하는 방법의 제어가 반전됩니다.오브젝트 ′a′는 오브젝트 ′b′ 참조를 취득할 책임을 지지 않습니다.대신 Configurator가 이를 담당합니다.이것이 IoC 디자인 패턴의 기본입니다.이 Configurator 객체의 장점을 구체적으로 나타내기 위해 이후에는 IoC를 사용하지 않는 설계와 IoC를 사용한 설계의 간단한 예를 소개합니다.리스트1과 그림1은 클래스 A가 클래스 B를 사용하는 설계의 간단한 예입니다.리스트1 클래스 A가 클래스 B를 직접 참조한다
public class A {
private B b ;
public A ( ) {
b = new B ( ) ;
}리스트 1은 다음과 같은 설계상의 조건을 전제로 하고 있습니다.클래스 A는 클래스 B에 참조를 필요로 한다
클래스 B는 디폴트 컨스트럭터를 갖는 구상 클래스이다
클래스 A는 클래스 B의 instance를 소유한다
다른 클래스는 클래스 B의 인스턴스에 접근할 수 없는 위의 조건이 어느 하나라도 변경된 경우, 리스트 1의 코드를 수정해야 합니다.예를 들어, 클래스 B의 설계를 변경하고, 디폴트 컨스트럭터를 사용하는 대신에 클래스 C(그림 2를 참조)를 받도록 했을 경우는, 리스트 1을 리스트 2와 같이 변경하게 됩니다.리스트 2 클래스 A가 클래스 B와 클래스 C를 직접 참조한다.
public class A {
private B b ;
public A ( ) {
C c = new C ( ) ;
b = new B ( c ) ;
}이 리스트 2도 몇 가지 설계상의 조건을 전제로 하고 있습니다.이번에는 객체 ′a′ 가 객체 ′b′ 와 객체 ′c′ 를 모두 소유하고 있습니다.클래스 B 또는 클래스 C를 큰폭으로 변경했을 경우는, 클래스 A도 수정의 필요가 있습니다.즉, 암묵적인 전제에 근거한 단순한 클래스의 단순한 설계에서는 장래적인 보수에 큰 부담이 갈 우려가 있습니다.여기서 소개한 것은 지극히 단순한 예이지만, 다양한 클래스를 사용하는 일반적인 어플리케이션이라면 변경이 얼마나 어려운지 쉽게 상상하실 수 있을 것입니다.반면 IoC 패턴을 사용하는 설계의 경우 객체 ′b′를 작성할 책임을 객체 ′a′에서 IoC 프레임워크로 옮기고 그 프레임워크로 객체 ′b′를 작성하여 객체 ′a′에 주입하도록 합니다.이것에 의해, 클래스 A를 클래스 B의 수정으로부터 분리할 수 있습니다.객체 ′a′는 객체 ′b′에 대한 참조를 필요로 하는데 이 점은 IoC 프레임워크가 객체 ′b′를 객체 ′a′에 주입함으로써 해결됩니다.리스트 3은 앞서 말한 리스트의 클래스 A를 IoC 패턴을 사용하도록 수정한 것입니다.리스트 3 클래스 A는 set B를 통해서 클래스 B에 대한 참조를 취득한다
public class A {
private B b ;
public A ( ) {
}
public setB ( B b ) {
this . b = b ;
}
}리스트 3은 다음과 같은 설계상의 조건을 전제로 하고 있습니다.A는 B를 참조할 필요가 있지만 B를 instance화하는 방법은 몰라도 된다
B는 인터페이스나 추상반이나 구상반이라도 좋다
클래스 A의 인스턴스를 사용하기 전에 클래스 B의 인스턴스에 대한 참조를 준비할 필요가 있는 이 설계상의 조건을 보면, 클래스 A와 클래스 B의 연결이 약해져 있는 것을 알 수 있습니다.어느 쪽의 클래스도, 상대에게 영향을 주지 않고 개별적으로 수정할 수 있습니다.물론, 클래스 B의 퍼블릭 메서드에 변경이 있었을 경우는, 클래스 A도 변경할 필요가 있습니다.그러나 객체 ′b′ 작성 방법과 관리 방법은 객체 ′a′ 구현 내에서는 정의되어 있지 않습니다.그 대신에 IoC 프레임워크가 오브젝트′a′내의 setB() 메서드를 사용해 오브젝트′b′를 주입합니다(그림 3 참조).IoC 프레임워크 종류 몇 개의 오픈소스 IoC 프레임워크(Spring, Pico Container, Hive Mind 등)는 IoC 패턴을 지원합니다.IoC의 기본 원칙은 단순하지만, 이들 프레임워크는 각각 다른 구현을 지원하고 있으며, 다른 이점을 실현하고 있습니다.IoC 패턴에는 3가지 구현방법이 있으며 각각 [Setter 베이스], [컨스트럭터 베이스], [인터페이스 베이스]로 불리고 있습니다.여기서는 각각의 방법에 대해서 간단히 설명하겠습니다.상세한 것에 대하여는, 각 프레임워크의 홈페이지를 참조해 주세요.이런 유형의 IoC에서는 Setter 메서드를 사용하여 참조되는 쪽의 객체를 참조하는 쪽의 객체에 주입합니다.이는 가장 일반적인 IoC로 Spring과 Pico Container는 이 방식을 구현하고 있습니다.′Setter 기반 IoC′는 옵션 파라미터를 받는 객체라던가 라이프 사이클 중에 속성을 자주 변경해야 되는 객체에 적합합니다.이 방식의 주요 단점은 Setter 메서드를 사용할 때 객체의 모든 속성을 외부에 공개해야 한다는 것입니다.이는 객체 지향의 기본 원칙인 [데이터 캡슐화]와 [정보 은폐]에 위배됩니다.이런 유형의 IoC에서는 컨스트럭터를 사용하여 객체에 대한 참조를 설정합니다.이 방식의 주요 장점은 참조되는 객체를 알고 있는 사람이 작성자 뿐이라는 것입니다.일단 객체가 생성되면 해당 객체를 사용하는 클라이언트 코드는 참조되는 객체를 인식하지 않습니다.이 방식은 모든 어플리케이션에서 사용할 수 있는 것은 아닙니다.예를 들어 디폴트 컨스트럭터를 필요로 하는 외부 API를 사용하려면 ′Setter 기반 IoC′를 사용해야 합니다.스프링 구현에서는 주로 ′컨스트럭터베이스 IoC′가 사용되고 있습니다.이런 유형의 IoC에서는 IoC 프레임워크의 특수한 인터페이스를 객체에 구현함으로써 IoC 프레임워크가 객체를 적절히 주입할 수 있도록 합니다.이 방식의 주요 장점은 객체 참조를 설정하기 위한 외부 설정 파일이 필요하지 않다는 것입니다.그 대신 IoC 프레임워크의 마커 인터페이스를 구현함으로써 오브젝트를 어떻게 결합할 것인가를 IoC 프레임워크에 인식시킵니다.이것은 EJB를 사용하는 것과 비슷합니다.EJB 컨테이너는 객체를 인스턴스화하여 자신에게 훅 시키는 방법을 인식하고 있습니다.이 방식의 주요 단점은 마커 인터페이스를 사용하기 때문에 특정 IoC 프레임워크와의 결합이 강해지는 것입니다.Apache Avalon은 이 방식을 근거로 하고 있지만, 이 프로젝트는 이미 폐쇄되어 있습니다.IoC의 예 새 프로젝트를 시작할 경우 필요에 따라 몇 개의 오픈 소스 IoC 프레임워크를 선택할 수 있습니다.하지만 기존 프로젝트에서 IoC 패턴을 사용할 때는 IoC를 지원하는 자체 클래스를 만들어야 합니다.오픈 소스의 IoC 프레임워크는 기존 컴포넌트나 충실한 기능을 제공하는데, 이를 사용하지 않고 IoC 패턴을 지원하는 일련의 클래스를 자체적으로 생성할 수도 있습니다.여기에서는 그 방법을 소개하겠습니다.예를 들어 고객 데이터를 처리하는 Customer Service 객체를 만들고 싶습니다.처리 대상 고객 데이터는 릴레이셔널 데이터베이스와 XML 파일 두 곳에 저장되어 있습니다.그리고 이 Customer Service 객체 안에서 데이터베이스 또는 XML 파일에서 데이터를 읽고 Customer 객체를 만들고 싶습니다.이 어플리케이션을 작성하면서 Customer Service의 소스 코드를 변경하지 않고 데이터 소스를 데이터베이스와 XML 파일 사이에서 전환할 수 있도록 하려면 어떤 설계를 해야 할까요.우선 인터페이스를 설계하고(리스트4 참조) 고객 데이터의 취득 및 저장에 사용할 공통 메서드를 정의합니다.XMLData Source 클래스와 JDBC Data Source 클래스는 모두 이 인터페이스를 구현합니다.리스트4 고객 데이터 읽고 쓰기에 사용하는 공통 인터페이스
public interface DataSource {
public Object retrieveObject ( ) ;
public void setDataSourceName ( String name ) ;
public String getDataSourceName ( ) ;
public void storeObject ( Object object ) ;
}} 목록 5의 XML Data Source 클래스는 DataSource 인터페이스를 구현하고 XML 파일에 대해 고객 데이터를 읽고 쓰는 구현을 작성합니다.리스트 5 XML 파일에서 고객 데이터를 가져오는 클래스
public class XMLDataSource implements DataSource {
private String name ;
/**
* Default Constructor
*/
public XMLDataSource ( ) {
super ( ) ;
}
/**
* Retrieve Customer data from XML Source and construct
* Customer object
*/
public Object retrieveObject ( ) {
// get XML data , parse it and then construct
// Customer object
return new Customer ( ′ XML ′ , 10 ) ;
}
/**
* Set the DataSource name
*/
public void setDataSourceName ( String name ) {
this.name=name;
}
/**
* Return DataSource name
*/
public String getDataSourceName ( ) {
return name ;
}
/**
* Store Customer into XML file
*/
public void storeObject ( Object object ) {
// Retrieve customer data and store it in
// XML file
}
}}리스트 6의 Relational Data Source 클래스도 Data Source 인터페이스를 구현해서 XML Data Source와 같은 구현을 만들지만 고객 데이터를 릴레이셔널 데이터베이스 간에 읽고 쓴다는 점만 다릅니다.리스트 6 릴레이셔널 데이터베이스에서 고객 데이터를 가져오는 클래스
public class RelationalDataSource implements DataSource {
private String name ;
/**
* Default constructor
*/
public RelationalDataSource ( ) {
super ( ) ;
}
/**
* Using the DataSource retrieve data for Customer and build a
* Customer object to return it to the caller
*/
public Object retrieveObject ( ) {
// get data for Customer object from DB and create a
// Customer object
return new Customer ( ′ Relational ′ , 10 ) ;
}
/**
* Set the DataSource name
*/
public void setDataSourceName ( String name ) {
this.name=name;
}
/**
* Return the name of the DataSource
*/
public String getDataSourceName ( ) {
return name ;
}
/**
* Store Customer into relational DB
*/
public void storeObject ( Object object ) {
// store the customer data into Relational DB
}
}}목록7의 Customer 클래스는 고객 데이터를 저장하는 단순한 클래스입니다.이 클래스는 XML Data Source 또는 Relational Dat Source 객체에 의해 생성됩니다.리스트 7 고객 데이터를 저장하는 클래스
public class Customer {
private String name ;
private int age ;
/**
* Default Constructor
*/
public Customer ( String name , int age ) {
this.name=name;
this . age = age ;
}
/**
* @ return Returns the age .
*/
public int getAge ( ) {
return age ;
}
/**
* @ param age The age to set .
*/
public void setAge ( int age ) {
this . age = age ;
}
/**
* @ return Returns the name .
*/
public String getName ( ) {
return name ;
}
/**
* @ param name The name to set .
*/
public void setName ( String name ) {
this.name = name ;
}
}}리스트 8에 Customer Service 클래스는 DataSource에 대한 참조를 받고, 이를 사용하여 Customer 객체의 취득 및 저장을 합니다.DataSource의 실제 구현은 XML Data Source 또는 Relational DataSource가 되겠습니다.어느 쪽이 될지는 어느 개체가 Customer Service 개체에 주입되느냐에 따라 결정됩니다.Customer Service 객체의 컨스트럭터는 DataSource 객체의 구상 구현을 받고 그것을 사용하여 고객 데이터를 읽고 씁니다.【8】서비스 구성자】【데이터 소스】【고객 서비스】▼】
공용 클래스 고객 서비스 {
개인 데이터 원본 데이터 원본;
개인 고객;
/**
* DataSource 개체가 주입되는 생성자. 기준
* ioc.properties 이 개체는 RelatelDataSource를 참조할 수 있습니다.
* XML 데이터 원본
*/
공개 고객 서비스(데이터 원본 데이터 원본) {
초강력;
this.dataSource=dataSource;
customer=(고객)dataSource.retrieveObject();
}
/**
* 고객명 수정
* @param 이름
*/
public void updateCustomerName(문자열 이름) {
손님.setName(이름);
}
/**
* 고객 연령 수정
* @paramage
*/
공개 보이드 업데이트고객 연령(유효 기간){
손님.연령 설정(나이);
}
/**
*
* @ 반송 고객명
*/
공용 문자열 getCustomerName(){
customer.getName();
}
/**
*
* @고객 연령 반환
*/
공용 상품 고객 연령(){
반품 고객.getAge();
}
}★9★서비스 구성자★★★iocパターンを使用してdatasourceの具象実装をcustomerserviceオブジェクトに注入するメインクラスです。このクラスは「ioc.properties」ファイルから設定パラメータを読み取り(図4を参照)、xmldatasourceオブジェクrviceオブジェクトはserviceconfiguratorのレジストリに保存されます。これ以降にcustomerserviceオブジェクトが要求されたときは、このオブジェクトが返されます。図4にserviceconfiguratorの内部的な処理を示します。図5は、ここまでにumlダイアグラムです。「ioc.properties」ファイルを編集するだけで、xmldatasourceとrelationaldatasourceを切り替えることができます。【9】【데이터 소스】【고객 서비스】】【서비스 구성자】
공용 클래스 서비스 구성자 {
공용 정적 최종 문자열 IoC_CONFIG_FILE=′ioc.properties′;
개인 특성 특성;
개인 HashMap 서비스 등록;
전용 정적 서비스 구성자 thisObject;
/**
* 이 방법은 먼저 ServiceConfigurator 인스턴스가 있는지 확인합니다.
* 존재하지 않는 경우 생성, 저장 및 반환
* @return
*/
공용 정적 서비스 구성자 서비스 구성자 만들기(){
if(이 개체==가 할당됨){
이 개체= 새 서비스 구성자();
}
이 개체를 반환합니다.
}
/**
* 개인 생성자가 이 클래스를 싱글톤으로 만듭니다.
*/
개인 서비스 구성자() {
소품 = 새 속성();
serviceRegistery=새 해시맵();
loadIoCConfig();
서비스 생성();
}
/**
* IoT 로드_CONFIG_FILE 속성 파일
*
*/
공용 void loadIoCConfig(){
입력 스트림은 =입니다.
this.getClass().getClassLoader().getResourceAsStream(IoC_CONFIG_FILE);
{}을(를) 시도하다
props.load(is);
is.closefilen;
} 캐치(FileNotFoundExceptione) {
e.인쇄 스택 트레이스();
} catch(IOException e) {
e.인쇄 스택 트레이스();
}
}
/**
* 에서 데이터 소스 이름을 가져와 고객 서비스를 만듭니다.
* 속성 파일. CustomerService 개체가 에 저장됩니다.
* service 요청 시 검색되도록 등록합니다.
* CustomerService를 구축하는 동안 DataSource 객체
*가 주입됩니다. 고객 서비스가 데이터 소스에 액세스할 수 있도록 지원
* 고객을 불러옵니다.
*
*/
public void createServices(){
문자열 dataSourceName=snmp.getProperty(′dataSource′);
데이터 원본 데이터 원본=px;
if(dataSourceName!=px){
{}을(를) 시도하다
데이터 원본=(데이터 원본)class.forName(dataSourceName).새 인스턴스();
}개 잡기(인스턴테이션)예외 e) {
e.인쇄 스택 트레이스();
}개 캐치(불법 액세스)예외 e) {
e.인쇄 스택 트레이스();
} 캐치(ClassNotFoundException e) {
e.인쇄 스택 트레이스();
}
}
//데이터 원본 이름이 속성 파일에서 검색됨
데이터 원본.데이터 원본 이름 설정(′props.getProperty(′name′)).;
고객 서비스 고객 서비스=새로운 고객 서비스(데이터 소스);
serviceRegistery.put(고객 서비스.클래스, 고객 서비스);
}
/**
* stored Service는 serviceRegistery에서 지정된 서비스를 검색합니다.
* 클래스 오브젝트
*
* @param classObj
* @return
*/
공용 개체 getService(ClassclassObj){
return serviceRegistery.get(classObj);
}
} 『서비스 구성자』iocパターンをサポートする単純なクラスです。これを修正して、複数のdatasourceオブジェクトを同時にサポートする機能や、datasourceをcustomerserviceに動的に注入する機能、ライフサイクル管理機能などを追加することjunitテストです。このテストでは、serviceconfiguratorを使用してcustomerserviceオブジェクトを取得し、このオブジェクトを使って顧客の名前と年齢を変更します。リスト10 customerserviceを使用する주니트
공개 클래스 CustomerServiceTester는 TestCase{를 확장합니다.
개인 서비스 구성자 서비스구성;
/**
*기본 생성자
*/
공개 고객 서비스 테스터() {
초강력;
}
/**
* 서비스 구성자 만들기
*/
public void setup()이 예외 {을(를) 발생시킵니다.
잘 하는 군요설정();
serviceConfig=ServiceConfigurator.createServiceConfigurator();
}
/**
* 고객 서비스 테스트 및 고객 확인
* @throws 예외
*/
public void testCustomerService()가 예외 {을(를) 발생시킵니다.
고객 서비스 고객 서비스 =
(CustomerService)serviceConfig.getService(CustomerService.class);
assertNotNull(custService);
custService.update고객 연령(30);
custService.updateCustomerName(′mani′);
acsertEquals(30,custService.getCustomerAge()),
assertEquals(′매니′, custService.getCustomerName();
}リスト9のserviceconfiguratorクラスの実装を見て、iocパターンを使用するserviceconfiguratorクラスと、service locatorパターンを使用するserviceconfiguratorクラスとではどこが違うのかと思った人もいるのではないでしょうか。実Service Locator 내부 구현을 변경하면 Customer Service가 Customer를 취득할 때 사용하는 DataSource를 전환할 수 있습니다.이 컨텍스트에서 Service Locator와 Service Configurator는 Customer Service에 대해 동일한 기능을 제공합니다.두 사람의 주된 차이점은 Customer Service가 DataSource 참조를 얻는 방법입니다.Service Locator의 경우에는 Customer Service가 Service Locator에서 DataSource 참조를 직접 가져와야 합니다.한편, Service Configurator의 경우 Customer Service는 Service Configurator 클래스를 통해 간접적으로 DataSource를 얻습니다.그렇다면 Service Locator에 비해 Service Configurator를 사용한 경우의 장점은 무엇일까요.Service Configurator를 사용한 경우에는 DataSource를 가져오거나 인스턴스화와 같은 처리를 Customer Service가 직접 담당하지 않아도 됩니다.Customer Service를 쉽게 테스트 할 수 있습니다.적절한 Data Source를 주입하기만 하면 되기 때문입니다.게다가 리스트 1과 리스트 2에서 설명한 대로 Customer Service가 DataSource 참조를 얻는 방법에 대한 전제조건이 없기 때문에 다양한 어플리케이션에서 Customer Service 오브젝트를 다시 이용할 수가 있습니다.이것이 Service Locator가 아닌 Service Configurator 사용의 주요 장점입니다.그림5에서 보시는 것처럼 Customer Service는 DataSource 클래스와 Customer 클래스 모두와 관계를 가지고 있습니다.이 글에서는 DataSource를 Customer Service에 넣는 방법을 설명해 드렸는데, Customer를 Customer Service에 넣지 않았을까요.실제로는 Customer용 인터페이스를 만들고 그것을 Customer Service 내에서 참조하도록 하고, Service Configurator가 Customer 객체를 Customer Service에 주입한다고 하는 설계를 해도 전혀 문제가 없습니다.그럴지의 여부는 어플리케이션의 요건과 Customer Service 클래스의 설계 방법에 따라 다릅니다.이번 예시는 Customer 객체의 작성과 저장을 DataSource에 담당시키고 있기 때문에 DataSource와 Customer가 밀접하게 연결이 되어 있습니다.나중에 Customer 클래스를 변경할 때는 DataSource의 모든 구현을 수정을 해야 합니다.그러나 이 설계는 Customer의 퍼블릭 메소드는 일절 변경되지 않으며 Customer 내부에서 변경이 이루어져도 DataSource에는 영향이 미치지 않는다는 것을 전제로 채용된 것입니다.이 어플리케이션에서 예상되는 것은 고객 데이터 수집/저장 방법 변경뿐입니다.현시점에서는 고객 데이터는 릴레이셔널 데이터베이스나 XML 파일에 저장되어 있습니다.미래에는 객체 데이터베이스에 저장을 하거나 웹 서비스를 통해서 취득을 할 가능성도 있지만, 어느 시나리오에서든 고객 데이터의 취득과 저장을 하는 새로운 클래스를 만들게 되면 대처할 수 있습니다.따라서 위의 전제를 바탕으로 이번 샘플에서는 DataSource만을 Customer Service에 주입을 하고, 이 객체를 사용하여 고객 데이터의 취득과 저장을 하고 있습니다.마지막으로 이 글에서는 IoC 패턴의 개요를 설명하고, 이 패턴을 사용하는 오픈 소스 프레임워크를 간단하게 소개해 드렸습니다.Service Configurator라고 하는 샘플을 통해서 기존 어플리케이션에 IoC 패턴을 도입하는 방법을 설명을 했습니다.Service Configurator는 단순한 수준이지만 IoC 패턴을 지원하며 이 패턴의 장점을 충분히 나타냅니다.필요에 따라 이 Service Configurator를 수정하고 다양한 기능을 추가해 보시기 바랍니다.
public class A {
private B b ;
public A ( ) {
b = new B ( ) ;
}리스트 1은 다음과 같은 설계상의 조건을 전제로 하고 있습니다.클래스 A는 클래스 B에 참조를 필요로 한다
클래스 B는 디폴트 컨스트럭터를 갖는 구상 클래스이다
클래스 A는 클래스 B의 instance를 소유한다
다른 클래스는 클래스 B의 인스턴스에 접근할 수 없는 위의 조건이 어느 하나라도 변경된 경우, 리스트 1의 코드를 수정해야 합니다.예를 들어, 클래스 B의 설계를 변경하고, 디폴트 컨스트럭터를 사용하는 대신에 클래스 C(그림 2를 참조)를 받도록 했을 경우는, 리스트 1을 리스트 2와 같이 변경하게 됩니다.리스트 2 클래스 A가 클래스 B와 클래스 C를 직접 참조한다.
public class A {
private B b ;
public A ( ) {
C c = new C ( ) ;
b = new B ( c ) ;
}이 리스트 2도 몇 가지 설계상의 조건을 전제로 하고 있습니다.이번에는 객체 ′a′ 가 객체 ′b′ 와 객체 ′c′ 를 모두 소유하고 있습니다.클래스 B 또는 클래스 C를 큰폭으로 변경했을 경우는, 클래스 A도 수정의 필요가 있습니다.즉, 암묵적인 전제에 근거한 단순한 클래스의 단순한 설계에서는 장래적인 보수에 큰 부담이 갈 우려가 있습니다.여기서 소개한 것은 지극히 단순한 예이지만, 다양한 클래스를 사용하는 일반적인 어플리케이션이라면 변경이 얼마나 어려운지 쉽게 상상하실 수 있을 것입니다.반면 IoC 패턴을 사용하는 설계의 경우 객체 ′b′를 작성할 책임을 객체 ′a′에서 IoC 프레임워크로 옮기고 그 프레임워크로 객체 ′b′를 작성하여 객체 ′a′에 주입하도록 합니다.이것에 의해, 클래스 A를 클래스 B의 수정으로부터 분리할 수 있습니다.객체 ′a′는 객체 ′b′에 대한 참조를 필요로 하는데 이 점은 IoC 프레임워크가 객체 ′b′를 객체 ′a′에 주입함으로써 해결됩니다.리스트 3은 앞서 말한 리스트의 클래스 A를 IoC 패턴을 사용하도록 수정한 것입니다.리스트 3 클래스 A는 set B를 통해서 클래스 B에 대한 참조를 취득한다
public class A {
private B b ;
public A ( ) {
}
public setB ( B b ) {
this . b = b ;
}
}리스트 3은 다음과 같은 설계상의 조건을 전제로 하고 있습니다.A는 B를 참조할 필요가 있지만 B를 instance화하는 방법은 몰라도 된다
B는 인터페이스나 추상반이나 구상반이라도 좋다
클래스 A의 인스턴스를 사용하기 전에 클래스 B의 인스턴스에 대한 참조를 준비할 필요가 있는 이 설계상의 조건을 보면, 클래스 A와 클래스 B의 연결이 약해져 있는 것을 알 수 있습니다.어느 쪽의 클래스도, 상대에게 영향을 주지 않고 개별적으로 수정할 수 있습니다.물론, 클래스 B의 퍼블릭 메서드에 변경이 있었을 경우는, 클래스 A도 변경할 필요가 있습니다.그러나 객체 ′b′ 작성 방법과 관리 방법은 객체 ′a′ 구현 내에서는 정의되어 있지 않습니다.그 대신에 IoC 프레임워크가 오브젝트′a′내의 setB() 메서드를 사용해 오브젝트′b′를 주입합니다(그림 3 참조).IoC 프레임워크 종류 몇 개의 오픈소스 IoC 프레임워크(Spring, Pico Container, Hive Mind 등)는 IoC 패턴을 지원합니다.IoC의 기본 원칙은 단순하지만, 이들 프레임워크는 각각 다른 구현을 지원하고 있으며, 다른 이점을 실현하고 있습니다.IoC 패턴에는 3가지 구현방법이 있으며 각각 [Setter 베이스], [컨스트럭터 베이스], [인터페이스 베이스]로 불리고 있습니다.여기서는 각각의 방법에 대해서 간단히 설명하겠습니다.상세한 것에 대하여는, 각 프레임워크의 홈페이지를 참조해 주세요.이런 유형의 IoC에서는 Setter 메서드를 사용하여 참조되는 쪽의 객체를 참조하는 쪽의 객체에 주입합니다.이는 가장 일반적인 IoC로 Spring과 Pico Container는 이 방식을 구현하고 있습니다.′Setter 기반 IoC′는 옵션 파라미터를 받는 객체라던가 라이프 사이클 중에 속성을 자주 변경해야 되는 객체에 적합합니다.이 방식의 주요 단점은 Setter 메서드를 사용할 때 객체의 모든 속성을 외부에 공개해야 한다는 것입니다.이는 객체 지향의 기본 원칙인 [데이터 캡슐화]와 [정보 은폐]에 위배됩니다.이런 유형의 IoC에서는 컨스트럭터를 사용하여 객체에 대한 참조를 설정합니다.이 방식의 주요 장점은 참조되는 객체를 알고 있는 사람이 작성자 뿐이라는 것입니다.일단 객체가 생성되면 해당 객체를 사용하는 클라이언트 코드는 참조되는 객체를 인식하지 않습니다.이 방식은 모든 어플리케이션에서 사용할 수 있는 것은 아닙니다.예를 들어 디폴트 컨스트럭터를 필요로 하는 외부 API를 사용하려면 ′Setter 기반 IoC′를 사용해야 합니다.스프링 구현에서는 주로 ′컨스트럭터베이스 IoC′가 사용되고 있습니다.이런 유형의 IoC에서는 IoC 프레임워크의 특수한 인터페이스를 객체에 구현함으로써 IoC 프레임워크가 객체를 적절히 주입할 수 있도록 합니다.이 방식의 주요 장점은 객체 참조를 설정하기 위한 외부 설정 파일이 필요하지 않다는 것입니다.그 대신 IoC 프레임워크의 마커 인터페이스를 구현함으로써 오브젝트를 어떻게 결합할 것인가를 IoC 프레임워크에 인식시킵니다.이것은 EJB를 사용하는 것과 비슷합니다.EJB 컨테이너는 객체를 인스턴스화하여 자신에게 훅 시키는 방법을 인식하고 있습니다.이 방식의 주요 단점은 마커 인터페이스를 사용하기 때문에 특정 IoC 프레임워크와의 결합이 강해지는 것입니다.Apache Avalon은 이 방식을 근거로 하고 있지만, 이 프로젝트는 이미 폐쇄되어 있습니다.IoC의 예 새 프로젝트를 시작할 경우 필요에 따라 몇 개의 오픈 소스 IoC 프레임워크를 선택할 수 있습니다.하지만 기존 프로젝트에서 IoC 패턴을 사용할 때는 IoC를 지원하는 자체 클래스를 만들어야 합니다.오픈 소스의 IoC 프레임워크는 기존 컴포넌트나 충실한 기능을 제공하는데, 이를 사용하지 않고 IoC 패턴을 지원하는 일련의 클래스를 자체적으로 생성할 수도 있습니다.여기에서는 그 방법을 소개하겠습니다.예를 들어 고객 데이터를 처리하는 Customer Service 객체를 만들고 싶습니다.처리 대상 고객 데이터는 릴레이셔널 데이터베이스와 XML 파일 두 곳에 저장되어 있습니다.그리고 이 Customer Service 객체 안에서 데이터베이스 또는 XML 파일에서 데이터를 읽고 Customer 객체를 만들고 싶습니다.이 어플리케이션을 작성하면서 Customer Service의 소스 코드를 변경하지 않고 데이터 소스를 데이터베이스와 XML 파일 사이에서 전환할 수 있도록 하려면 어떤 설계를 해야 할까요.우선 인터페이스를 설계하고(리스트4 참조) 고객 데이터의 취득 및 저장에 사용할 공통 메서드를 정의합니다.XMLData Source 클래스와 JDBC Data Source 클래스는 모두 이 인터페이스를 구현합니다.리스트4 고객 데이터 읽고 쓰기에 사용하는 공통 인터페이스
public interface DataSource {
public Object retrieveObject ( ) ;
public void setDataSourceName ( String name ) ;
public String getDataSourceName ( ) ;
public void storeObject ( Object object ) ;
}} 목록 5의 XML Data Source 클래스는 DataSource 인터페이스를 구현하고 XML 파일에 대해 고객 데이터를 읽고 쓰는 구현을 작성합니다.리스트 5 XML 파일에서 고객 데이터를 가져오는 클래스
public class XMLDataSource implements DataSource {
private String name ;
/**
* Default Constructor
*/
public XMLDataSource ( ) {
super ( ) ;
}
/**
* Retrieve Customer data from XML Source and construct
* Customer object
*/
public Object retrieveObject ( ) {
// get XML data , parse it and then construct
// Customer object
return new Customer ( ′ XML ′ , 10 ) ;
}
/**
* Set the DataSource name
*/
public void setDataSourceName ( String name ) {
this.name=name;
}
/**
* Return DataSource name
*/
public String getDataSourceName ( ) {
return name ;
}
/**
* Store Customer into XML file
*/
public void storeObject ( Object object ) {
// Retrieve customer data and store it in
// XML file
}
}}리스트 6의 Relational Data Source 클래스도 Data Source 인터페이스를 구현해서 XML Data Source와 같은 구현을 만들지만 고객 데이터를 릴레이셔널 데이터베이스 간에 읽고 쓴다는 점만 다릅니다.리스트 6 릴레이셔널 데이터베이스에서 고객 데이터를 가져오는 클래스
public class RelationalDataSource implements DataSource {
private String name ;
/**
* Default constructor
*/
public RelationalDataSource ( ) {
super ( ) ;
}
/**
* Using the DataSource retrieve data for Customer and build a
* Customer object to return it to the caller
*/
public Object retrieveObject ( ) {
// get data for Customer object from DB and create a
// Customer object
return new Customer ( ′ Relational ′ , 10 ) ;
}
/**
* Set the DataSource name
*/
public void setDataSourceName ( String name ) {
this.name=name;
}
/**
* Return the name of the DataSource
*/
public String getDataSourceName ( ) {
return name ;
}
/**
* Store Customer into relational DB
*/
public void storeObject ( Object object ) {
// store the customer data into Relational DB
}
}}목록7의 Customer 클래스는 고객 데이터를 저장하는 단순한 클래스입니다.이 클래스는 XML Data Source 또는 Relational Dat Source 객체에 의해 생성됩니다.리스트 7 고객 데이터를 저장하는 클래스
public class Customer {
private String name ;
private int age ;
/**
* Default Constructor
*/
public Customer ( String name , int age ) {
this.name=name;
this . age = age ;
}
/**
* @ return Returns the age .
*/
public int getAge ( ) {
return age ;
}
/**
* @ param age The age to set .
*/
public void setAge ( int age ) {
this . age = age ;
}
/**
* @ return Returns the name .
*/
public String getName ( ) {
return name ;
}
/**
* @ param name The name to set .
*/
public void setName ( String name ) {
this.name = name ;
}
}}리스트 8에 Customer Service 클래스는 DataSource에 대한 참조를 받고, 이를 사용하여 Customer 객체의 취득 및 저장을 합니다.DataSource의 실제 구현은 XML Data Source 또는 Relational DataSource가 되겠습니다.어느 쪽이 될지는 어느 개체가 Customer Service 개체에 주입되느냐에 따라 결정됩니다.Customer Service 객체의 컨스트럭터는 DataSource 객체의 구상 구현을 받고 그것을 사용하여 고객 데이터를 읽고 씁니다.【8】서비스 구성자】【데이터 소스】【고객 서비스】▼】
공용 클래스 고객 서비스 {
개인 데이터 원본 데이터 원본;
개인 고객;
/**
* DataSource 개체가 주입되는 생성자. 기준
* ioc.properties 이 개체는 RelatelDataSource를 참조할 수 있습니다.
* XML 데이터 원본
*/
공개 고객 서비스(데이터 원본 데이터 원본) {
초강력;
this.dataSource=dataSource;
customer=(고객)dataSource.retrieveObject();
}
/**
* 고객명 수정
* @param 이름
*/
public void updateCustomerName(문자열 이름) {
손님.setName(이름);
}
/**
* 고객 연령 수정
* @paramage
*/
공개 보이드 업데이트고객 연령(유효 기간){
손님.연령 설정(나이);
}
/**
*
* @ 반송 고객명
*/
공용 문자열 getCustomerName(){
customer.getName();
}
/**
*
* @고객 연령 반환
*/
공용 상품 고객 연령(){
반품 고객.getAge();
}
}★9★서비스 구성자★★★iocパターンを使用してdatasourceの具象実装をcustomerserviceオブジェクトに注入するメインクラスです。このクラスは「ioc.properties」ファイルから設定パラメータを読み取り(図4を参照)、xmldatasourceオブジェクrviceオブジェクトはserviceconfiguratorのレジストリに保存されます。これ以降にcustomerserviceオブジェクトが要求されたときは、このオブジェクトが返されます。図4にserviceconfiguratorの内部的な処理を示します。図5は、ここまでにumlダイアグラムです。「ioc.properties」ファイルを編集するだけで、xmldatasourceとrelationaldatasourceを切り替えることができます。【9】【데이터 소스】【고객 서비스】】【서비스 구성자】
공용 클래스 서비스 구성자 {
공용 정적 최종 문자열 IoC_CONFIG_FILE=′ioc.properties′;
개인 특성 특성;
개인 HashMap 서비스 등록;
전용 정적 서비스 구성자 thisObject;
/**
* 이 방법은 먼저 ServiceConfigurator 인스턴스가 있는지 확인합니다.
* 존재하지 않는 경우 생성, 저장 및 반환
* @return
*/
공용 정적 서비스 구성자 서비스 구성자 만들기(){
if(이 개체==가 할당됨){
이 개체= 새 서비스 구성자();
}
이 개체를 반환합니다.
}
/**
* 개인 생성자가 이 클래스를 싱글톤으로 만듭니다.
*/
개인 서비스 구성자() {
소품 = 새 속성();
serviceRegistery=새 해시맵();
loadIoCConfig();
서비스 생성();
}
/**
* IoT 로드_CONFIG_FILE 속성 파일
*
*/
공용 void loadIoCConfig(){
입력 스트림은 =입니다.
this.getClass().getClassLoader().getResourceAsStream(IoC_CONFIG_FILE);
{}을(를) 시도하다
props.load(is);
is.closefilen;
} 캐치(FileNotFoundExceptione) {
e.인쇄 스택 트레이스();
} catch(IOException e) {
e.인쇄 스택 트레이스();
}
}
/**
* 에서 데이터 소스 이름을 가져와 고객 서비스를 만듭니다.
* 속성 파일. CustomerService 개체가 에 저장됩니다.
* service 요청 시 검색되도록 등록합니다.
* CustomerService를 구축하는 동안 DataSource 객체
*가 주입됩니다. 고객 서비스가 데이터 소스에 액세스할 수 있도록 지원
* 고객을 불러옵니다.
*
*/
public void createServices(){
문자열 dataSourceName=snmp.getProperty(′dataSource′);
데이터 원본 데이터 원본=px;
if(dataSourceName!=px){
{}을(를) 시도하다
데이터 원본=(데이터 원본)class.forName(dataSourceName).새 인스턴스();
}개 잡기(인스턴테이션)예외 e) {
e.인쇄 스택 트레이스();
}개 캐치(불법 액세스)예외 e) {
e.인쇄 스택 트레이스();
} 캐치(ClassNotFoundException e) {
e.인쇄 스택 트레이스();
}
}
//데이터 원본 이름이 속성 파일에서 검색됨
데이터 원본.데이터 원본 이름 설정(′props.getProperty(′name′)).;
고객 서비스 고객 서비스=새로운 고객 서비스(데이터 소스);
serviceRegistery.put(고객 서비스.클래스, 고객 서비스);
}
/**
* stored Service는 serviceRegistery에서 지정된 서비스를 검색합니다.
* 클래스 오브젝트
*
* @param classObj
* @return
*/
공용 개체 getService(ClassclassObj){
return serviceRegistery.get(classObj);
}
} 『서비스 구성자』iocパターンをサポートする単純なクラスです。これを修正して、複数のdatasourceオブジェクトを同時にサポートする機能や、datasourceをcustomerserviceに動的に注入する機能、ライフサイクル管理機能などを追加することjunitテストです。このテストでは、serviceconfiguratorを使用してcustomerserviceオブジェクトを取得し、このオブジェクトを使って顧客の名前と年齢を変更します。リスト10 customerserviceを使用する주니트
공개 클래스 CustomerServiceTester는 TestCase{를 확장합니다.
개인 서비스 구성자 서비스구성;
/**
*기본 생성자
*/
공개 고객 서비스 테스터() {
초강력;
}
/**
* 서비스 구성자 만들기
*/
public void setup()이 예외 {을(를) 발생시킵니다.
잘 하는 군요설정();
serviceConfig=ServiceConfigurator.createServiceConfigurator();
}
/**
* 고객 서비스 테스트 및 고객 확인
* @throws 예외
*/
public void testCustomerService()가 예외 {을(를) 발생시킵니다.
고객 서비스 고객 서비스 =
(CustomerService)serviceConfig.getService(CustomerService.class);
assertNotNull(custService);
custService.update고객 연령(30);
custService.updateCustomerName(′mani′);
acsertEquals(30,custService.getCustomerAge()),
assertEquals(′매니′, custService.getCustomerName();
}リスト9のserviceconfiguratorクラスの実装を見て、iocパターンを使用するserviceconfiguratorクラスと、service locatorパターンを使用するserviceconfiguratorクラスとではどこが違うのかと思った人もいるのではないでしょうか。実Service Locator 내부 구현을 변경하면 Customer Service가 Customer를 취득할 때 사용하는 DataSource를 전환할 수 있습니다.이 컨텍스트에서 Service Locator와 Service Configurator는 Customer Service에 대해 동일한 기능을 제공합니다.두 사람의 주된 차이점은 Customer Service가 DataSource 참조를 얻는 방법입니다.Service Locator의 경우에는 Customer Service가 Service Locator에서 DataSource 참조를 직접 가져와야 합니다.한편, Service Configurator의 경우 Customer Service는 Service Configurator 클래스를 통해 간접적으로 DataSource를 얻습니다.그렇다면 Service Locator에 비해 Service Configurator를 사용한 경우의 장점은 무엇일까요.Service Configurator를 사용한 경우에는 DataSource를 가져오거나 인스턴스화와 같은 처리를 Customer Service가 직접 담당하지 않아도 됩니다.Customer Service를 쉽게 테스트 할 수 있습니다.적절한 Data Source를 주입하기만 하면 되기 때문입니다.게다가 리스트 1과 리스트 2에서 설명한 대로 Customer Service가 DataSource 참조를 얻는 방법에 대한 전제조건이 없기 때문에 다양한 어플리케이션에서 Customer Service 오브젝트를 다시 이용할 수가 있습니다.이것이 Service Locator가 아닌 Service Configurator 사용의 주요 장점입니다.그림5에서 보시는 것처럼 Customer Service는 DataSource 클래스와 Customer 클래스 모두와 관계를 가지고 있습니다.이 글에서는 DataSource를 Customer Service에 넣는 방법을 설명해 드렸는데, Customer를 Customer Service에 넣지 않았을까요.실제로는 Customer용 인터페이스를 만들고 그것을 Customer Service 내에서 참조하도록 하고, Service Configurator가 Customer 객체를 Customer Service에 주입한다고 하는 설계를 해도 전혀 문제가 없습니다.그럴지의 여부는 어플리케이션의 요건과 Customer Service 클래스의 설계 방법에 따라 다릅니다.이번 예시는 Customer 객체의 작성과 저장을 DataSource에 담당시키고 있기 때문에 DataSource와 Customer가 밀접하게 연결이 되어 있습니다.나중에 Customer 클래스를 변경할 때는 DataSource의 모든 구현을 수정을 해야 합니다.그러나 이 설계는 Customer의 퍼블릭 메소드는 일절 변경되지 않으며 Customer 내부에서 변경이 이루어져도 DataSource에는 영향이 미치지 않는다는 것을 전제로 채용된 것입니다.이 어플리케이션에서 예상되는 것은 고객 데이터 수집/저장 방법 변경뿐입니다.현시점에서는 고객 데이터는 릴레이셔널 데이터베이스나 XML 파일에 저장되어 있습니다.미래에는 객체 데이터베이스에 저장을 하거나 웹 서비스를 통해서 취득을 할 가능성도 있지만, 어느 시나리오에서든 고객 데이터의 취득과 저장을 하는 새로운 클래스를 만들게 되면 대처할 수 있습니다.따라서 위의 전제를 바탕으로 이번 샘플에서는 DataSource만을 Customer Service에 주입을 하고, 이 객체를 사용하여 고객 데이터의 취득과 저장을 하고 있습니다.마지막으로 이 글에서는 IoC 패턴의 개요를 설명하고, 이 패턴을 사용하는 오픈 소스 프레임워크를 간단하게 소개해 드렸습니다.Service Configurator라고 하는 샘플을 통해서 기존 어플리케이션에 IoC 패턴을 도입하는 방법을 설명을 했습니다.Service Configurator는 단순한 수준이지만 IoC 패턴을 지원하며 이 패턴의 장점을 충분히 나타냅니다.필요에 따라 이 Service Configurator를 수정하고 다양한 기능을 추가해 보시기 바랍니다.
반응형