Pattern[4/4]

|

이번호에 앞서

지난 호까지 디자인 패턴에 관해서 여러 이야기들을 하여 왔다.

이번 호는 마지막으로 자바에서의 디자인 패턴의 적용을 정리하고 4회에 걸친 디자인 패턴에 관련된 맺음말로 글을 마무리 하려 한다.

이번 호에서 소개하는 부분은 2가지이다. 하나는 자바 스페이스에서의 분산 이벤트 모델의 디자인 패턴이며 다른 하나는 커넥터 아키텍쳐에서의 디자인 패턴이다. 전자는 분산 객체 환경인 자바 스페이스에서 나타나는 이벤트 모델 내부에서 사용되는 디자인 패턴에 관련된 설명이며, 후자는 커낵터라는 이름이 의미하는 것처럼 임의의 EIS(Enterpirse Information System)에 대한 접근을 일반화하시키자는 데에 그 목적이 있다.

커넥터와 자바스페이스 모두 독자들에게 생소한 기술인 관계로 이해에 다소 어려움이 있더라도 양해를 바란다.

다자인 패턴 IN JAVA SPACE

자바 스페이스는 JINI의 일 부분으로 분산 객체가 존재하는 공간이다. 자바 스페이스에는 객체들이 저장되고 변경되기 위한 몇 가지 인터페이스를 제공한다. 자바 스페이스의 개념은 매우 간단하다.

u       자바 스페이스라 불리우는 공간이 있다.

u       객체는 공간에 저장될 수 있다.

u       공간에 존재하는 객체를 사용하기 위한 어플리케이션은 객체를 공간에서 꺼내와야 한다.

u       어플리케이션이 사용 후 변경된 객체를 공간에 반영하기 위해서는 객체를 공간에 저장하여야 한다.

u       트랜잭션은 객체를 공간에 저장하는 시점에서 발생한다.

로 요약될 수 있다.

원격지에서 동떨어진 어플리케이션들은 자바 스페이스를 주시하고, 객체들이 추가되거나 삭제되는 등의 액션에 따라 함께 반응한다.

지금까지의 연재 기사를 읽어온 독자분들 중에서 자바 스페이스에서 디자인 패턴의 개념이 사용되었을까라는 질문에 NO라고 답할 분은 아마 존재하지 않으실 것이다.

그러하다. 자바 스페이스에서도 당연히 디자인 패턴이 존재한다. 자바 스페이스는 분산 컴퓨팅 환경에서 객체들이 상호 연동되는 연동의 장이다.

이러한 분산 객체 환경에서 디자인 패턴이 어떻게 사용될까? 디자인 패턴을 사용할 수 있는 개념을 적용할 수 있는 분야에는 분산 객체 환경이 포함된다.

디자인 패턴이 진정으로 중요한 이유중의 하나는 새로운 기술들인 분산 객체 환경에서도 디자인 패턴이 여지 없이 사용되고 있다는 사실 그 자체이다. 많은 사람들이 하나의 기술을 배우면 2~3년 내에 사장되어 버리고 새로운 기술을 배울 수 없는 것이 아닌가? 하는 걱정을 내심 가지고 있다. C 언어는 이미 사장 되어 가는 기술이며, PHP,CGI등의 기술도 이미 JSP나 ASP등의 신기술에 맞물려 조금씩 사라지고 있다.

그러나, 원천적인 기술은 변하지 않는다. 디자인 패턴은 이미 94년도에 최초로 널리 알려진 기법이지만 지금까지도 그다지 변하지 않는 기술이며, 앞으로도 핵심적인 기술로 사용될 것이다.

자바 스페이스에서의 이벤트 모델에 있어 디자인 패턴이 어떻게 적용되어 왔는가를 발견하는 과정을 정리하여 다음에 기술한다. 필자가 자바 스페이스를 공부하면서 이중에서 디자인 패턴을 발견하고 시스템을 더욱 이해하게 되는 과정을 상세하게 기술하여, 디자인 패턴이 프로그램을 이해하는 측면에서 어떻게 활용되는가를 설명한다.

JAVA SPACE & Distributed Event Model 

자바 스페이스는 분산 객체에 중점적으로 맞추어져 있다는 사실을 명심하라.

분산 환경에서는 강하게 연결된 (Tightly Coupled ) 커뮤니케이션이 어렵다.

분산 환경에서 발생하는 주요한 이슈중의 하나는 분산 이벤트 모델이다. 하나의 객체에서 발생하는 이벤트를 어떻게 효과적으로 주고 받을 수 있는 환경을 만들 수 있는가?

자바의 최신 기술 중에서 자바 스페이스는 매우 재미있는 분산 이벤트 모델 부분들을 포함하고 있다.

 

 

JAVA에서의 분산 이벤트 모델의 이해의 시작.

자바 스페이스의 개념 설명에 등장하는 자바 스페이스는 이벤트들이 이벤트 핸들러에게 다이나믹하게 전달될 수 있는 장을 마련한다.라는 어휘에서 필자는 다음의 몇가지 항목들을 생각할 수 있었다.

 

1.       이벤트를 처리하는 객체는 미리 정의되어 있는 것이 아니라, 런타임 시점에서 다이나믹하게 정의된다. à Dynamic Linkage Pattern

 

2.       이벤트를 처리하는 객체는 이미 추상 클래스 혹은 인터페이스로 운용 방법이 지정되어 있다. à Adaptor Pattern

 

이러한 개념은 디자인 패턴에서 나타나는 개념으로 프로그램의 경험이 어느 수준이상 되는 개발자라면 어느 정도 짐작이 갈 부분들이다.

 

필자는 여기서 Dynamic Linkage Pattern과 Adaptor Pattern의 개념으로 이벤트를 처리하는 과정을 이해하자라고 어느 정도 예상하고 있었다.[Dynamic Linkage Pattern과 Adaptor Pattern에 관한 상세한 내용은 박스기사를 참고하기 바란다.]

 

이벤트 시스템의 개념을 대략적으로 나마 파악하고 시스템을 이해하기 시작하는 과정은 프로그램의 구조에 대한 개념이 전혀 없는 상태에서 이해하기 시작하는 것보다 시스템 전반적인 상황을 예측하고 이해를 시작하는 과정이 2배 이상의 수월한 작업이다.

 

분산 이벤트 모델과 디자인 패턴

자바 스페이스에서의 이벤트 모델은 색다른 구조를 가지고 있다. 자바 스페이스의 분산 이벤트 모델에서 이벤트는 자바 스페이스 내부에서 발생한다. 발생 된 이벤트는 분산 객체에 의해서 처리된다.

 

이벤트의 개념을 간단히 이야기 하면, 특정 사건에 의해서 이벤트가 발생하고 발생된 이벤트는 사전에 등록된 이벤트 리스너에 의해서 핸들링 된다는 개념이다.

자바 스페이스의 이벤트 모델에서의 다음의 순서로 발생한다.

1.       이벤트 리스너 생성

2.       JavaSpace의 Notify 메소드를 이용한 이벤트 종류와 핸들링하는 이벤트 리스너 등록

3.       JavaSpace에 이벤트 종류로 등록된 객체가 저장되어 이벤트가 발생

4.       발생된 이벤트는 등록된 이벤트 리스너에 의해서 처리된다.

4가지 순서로 표현된다.

 

여기서 한가지 눈 여겨서 볼 것은 이벤트라는 것이 별도로 지정되는 것이 아니라는 점이다. 이벤트는 외부의 어플리케이션이 명시적으로 지정하여 발생시키는 것이 아니며, 이벤트 객체를 생성하여 던지는 것도 아니다. 오직 자바 스페이스에 객체가 생성되어 저장(Write)되는 순간 발생한다. 만약 자바 스페이스에 저장되는 객체가 이벤트로 등록이 된 객체라면, 해당 이벤트가 내부적으로 발생하게 되며, 이를 처리하는 이벤트 처리기가 이벤트의 처리를 담당할 것이다.

 

분산 이벤트 모델에서 이들을 구현하기 위해서 자바 스페이스들은 다음의 구성 요소들을 제공한다.

 

Event Listener 생성에서는 미리 정의된 이벤트 리스너의 인터페이스에 맞추어 이벤트를 정의한다. 이벤트의 등록 시점에서는 새로이 생성할 이벤트의 인스턴스를 임의로 생성하여 자바 스페이스에 리스너와 함께 등록한다. 이 시점에서 해당 이벤트 클래스의 이벤트 리스너는 등록된 리스너로 지정된다.

 

분산 이벤트 모델 & Dynamic Linkage Pattern

자바 스페이스 뿐만이 아니라 다른 대부분의 이벤트 모델에서는 동적 연결 패턴이 흔히 사용된다. 이벤트의 종류와 이를 처리하기 위한 이벤트 리스너는 정적인 연결 관계로 정의될 수 없기 때문에 이를 동적으로 연결하기 위한 방안으로 동적 연결 패턴을 사용한다.

 이를 이용한 이벤트 모델은 이벤트의 종류와 이를 처리하기 위한 이벤트 리스너의 연관 관계가 해쉬테이블이나 Mapping 테이블의 형태로 저장되며, 이벤트가 발생하는 시점에서 리스너의 정보를 테이블에서 찾아 리스너를 수행시키는 형태로 동작한다.

1.        그림 1이벤트 등록 관계 클래스 다이어그램

위의 클래스 다이어그램은 이벤트 등록에 관계된 여러 클래스들의 연관 관계를 보여준다. 이벤트 들은 TemplateHolderSet이라는 집합에 등록된다. 이벤트들은 모두 자바 스페이스에 저장되는 객체들의 종류인 Entry 객체를 상속받는다. TemplateHolderSet에서는 이들 Entry 객체의 종류를 저장한다.

 

public EventRegistration notify(EntryRep tmpl, Transaction tr, RemoteEventListener listener, long leaseTime, MarshalledObject handback) throws TransactionException, RemoteException {

    ---

}

이벤트의 등록은 자바스페이스의 Notify 메소드를 이용하여 처리된다. 여기서 Notify는 이벤트가 발생되었을 때 호출되는 함수가 아님을 명심해야 한다. 자바 스페이스의 Notify 메소드는 분명 이벤트 종류와 이벤트 리스너를 등록하기 위한 것으로 이벤트 리스너와 이벤트 객체를 파라메터로 포함하고 있다. 트랜잭션과 마샬링에 관한 설명은 이번 호의 범위를 벗어난 것으로 생략한다.

 

/**

 * Private implementation of add for the handle and chit pair.

 */

private void add(TemplateHandle handle, NotifyChit chit) {

  EntryRep tmpl = handle.rep();

  if (tmpl.id() == tmpl.ID_UNSET) {

      synchronized (this) {        // protect this id field

        if (tmpl.id() == tmpl.ID_UNSET)  // in case someone beat us to it

            tmpl.id(BasicSpace.nextID());

      }

  }

  // Now that the chit is all set up we can add it to our tables

  idMap.put(chit.getCookie(), chit); ß 이벤트 처리기는 이벤트 객체의 종류와 함께 Chit(증명서)라는 형태로 가공되어 ID Mapping Hashtable에 저장된다.

  handle.add(chit);

}

 

위의 예제는 Sun의 JavaSpace의 소스코드에서 EventListener가 등록되는 부분인 Add 메소드의 소스 코드이다. idMap.put에서 결국 Dynamic Linkage Pattern으로 귀결된다.

 이벤트가 생성되면, idMap상의  이벤트 이벤트 종류의 ID에서 이를 처리하는 이벤트 리스너의 정보를 검색하여 해당 이벤트 리스너의 Notify 메서드를 호출한다. 이벤트 리스너는 모두 Notify[1]메소드를 구현해야 한다.

 

분산 이벤트 모델 & Adaptor

 

동적 연결 패턴은 당연하게 Adaptor패턴과 연관 되어 사용된다.

자바 스페이스에서의 이벤트 처리 내부 동작은 동적 연결 패턴을 이용하여 등록된 이벤트 리스너를 획득하여 자바 스페이스 시스템이 해당 이벤트 리스너의 Notify메소드를 호출하여 처리된다.

 

리모트 이벤트 리스너의 Constructor와 Notify 메소드의 구현은 분명 Adaptor Pattern으로 모두 RemoteEventListener 인터페이스를 상속하여 구현된다.

 

2.        그림 2 – Adaptor 패턴을 이용한 이벤트 리스너 구현 클래스 다이어그램

자바 스페이스와 자바 스페이스의 이벤트를 연결하여 주는 RemoteEventListner는 Interface로 정의되어 있지만, 이를 구현하는 객체는 RMI를 이용하여 RemoteEventListener를 정의한다. 이는 다음의 예제를 참조하라.

 

public class ExampleListener implements RemoteEventListener {

  private JavaSpace space;

 

  public Listener(JavaSpace space) throws RemoteException {

        this.space = space;

        UnicastRemoteObject.exportObject(this);   

  }

 

  public void notify(RemoteEvent ev) {

        Message template = new Messgae();

        try {

               Message result =

                      (Message)space.read(template,null,long.MAX_VALUE);

               System.out.println(result.content);

        } catch( Exception e) {

               e.printStackTrace();

        }

  }

}

 

위의 예제에서 Constructor는 RMI의 처리를 담당하고, Notify에서 이벤트의 처리를 수행한다.

 

분산 이벤트 모델과 RMI

자바 스페이스 분산 이벤트 모델에서 분산 이벤트의 처리는 RMI(Remote Method Invocation)에 근간을 두고 있다. 하나의 자바 스페이스는 다른 RemoteEventListener 인터페이스를 통하여 다른 자바 스페이스에 존재하는 객체들을 이벤트 리스너로 등록 할 수 있다. 이때, RMI Proxy를 이용하여 RMI의 Proxy 객체를 이벤트가 발생하는 자바 스페이스에 등록한다.

 

자바 스페이스에서 발생된 이벤트는 Proxy객체를 통하여 원격지에 있는 객체에게 전송되고, 원격지에서 이를 처리한다.[RMI와 Proxy 패턴의 상호관계에 관한 설명은 11월 호의 기사를 참조하기 바란다.]

 

분산 이벤트 모델의 이해와 디자인 패턴

패턴에 대한 사전 지식이 자바 스페이스의 이해에 도움을 분명 주었을까?

필자는 아직까지 자바 스페이스에 관해서 심도 있는 이해를 하고 있지 않다. 다만 분산 컴퓨팅환경에서 객체들을 담아두고 관리하기 위한 저장소 정도의 개념으로만 생각하고 있었다. 

그러나 분산 컴퓨팅 환경 중에서 중요한 요소인 분산 이벤트 처리의 방법을 다루면서 프로그램의 이해에 많은 고민과 고통이 뒤따라 왔다. 몇 일을 밤을 새면서 글을 읽고 있으면, 어느 순간인가 갑자기 이해가 되는 부분들이 있다.

대개의 경우, 이러한 부분들은 패턴의 개념이 적용된 부분들이다. 리모트 이벤트 리스너 등에서 사용된 Proxy기법까지의 패턴이 파악되는 순간, 분산 컴퓨팅 환경에서 이벤트 처리가 어떻게 이루어 지는지에 관한 기본 수준의 이해를 하게 되었다.

 

패턴은 분명 새로운 기술과 프로그래밍 개념의 이해를 돕는데 많은 도움이 된다.

 

 

커넥터 아키텍쳐(Connector Architecture) 디자인 패턴

커넥터 아키텍쳐란 무엇인가?

 

커넥터 아키텍쳐는 이 글을 쓰는 현재 JCP(Java Community Process)의 Final Draft 단계 에 있으며 같이 진행되고 있는 J2EE 1.3 스펙에 포함될 예정으로 있다. 커넥터 아키텍쳐는 이름이 의미하는 것처럼 임의의 EIS(Enterpirse Information System)에 대한 접근을 일반화하시키자는 데에 그 목적이 있다. 시스템을 개발할때에 매일 사용하고 있는 데이터베이스에서부터 ERP, TP Monitor, Groupware, KMS등도 모두 EIS라고 할 수 있다. 자 그럼 지금까지  EIS와 연동되는 프로그램 혹은 서버를 개발하기 위해 어떤 일을 해 왔는지 한번 상기해 보는 의미에서 그림 3을 살펴 보도록 한다.

 

[그림 3 ERP, TP System에 대한 기존 개발 구조도]

그림 3은 구매 시스템에서의 PurchaseOrder EJB를 개발했을때의 구조도이다. 이 그림에서의 애플리케이션 서버란 EJB Container와 JSP/Servlet Container등을 모두 포함하는 J2EE 기반의 애플리케이션 서버이다. PurchaseOrder EJB에서는 ERP 와 TP System에 접근을 해야 한다. 이것은 보통 ERP나 TP System에서 제공하는 SDK와 같은 API Set을 통해 이루어진다. 즉 비즈니스 로직을 개발할때는  EIS에서 개별적으로 제공하는 API를 이용하므로 EIS 갯수만큼의 인터페이스 로직이 필요하게 된다.

이번에는 애플리케이션 서버입장에서 살펴 보자. 트랜잭션 조절이나 Connection Pool과 같은 관리의 이유로 애플리케이션 서버 또한 EIS에 접근할 필요가 있다. 역시 이경우에도 EIS에서 제공하는 Administration API를 이용해야 하기 때문에 EIS 개수만큼의 인터페이스 로직이 필요하게 된다.

아마도 필자를 포함해 많은 사람들은 이러한 것을 당연시 여겨왔다. 이것은 두가지 이유가 있는데 첫째는 EIS에 접근하기 위한 일반화된 연결고리가 정의되어져 있지 않았다는 것이고 두번째는 EIS의 다양한 기능을 위한 일반화된 접근법이 정의되어질 수 있겠느냐는 의문이 있어 왔다는 점이다. 

하나의 예를 들어 보자. 많은 DB 벤더들은 나름대로의 특화된 API를 제공하였고 자신의 제품이 모든 DB에서 동작한다고 선전하는 회사의 개발팀에서는 별개의 DB마다 릴리즈하기 위해 특별한 밤들을 추가적으로 새워야 했다. ODBC/JDBC의 등장으로 개발자들의 많은 역할들이 드라이버 벤더들에게 넘어갔고 덕분에 제품 개발팀은 매우 빠른시간에 복수 DB에 대한 개발을 가능해 졌다.

과연 다양한 EIS에 대해서도 일반화된 연결고리가 정의되어질 수 있을 것인가? 동시에 각 EIS마다 특화된 기능들을 자연스럽게 사용할 수 있을까? 이 질문의 해답에 대한 도전이 바로 커넥터 아키텍쳐이다. 그림4를 보자.

 

[그림 4 커넥터 아키텍쳐상에서의 ERP, TP System에 대한 개발 구조도]

모든 것에 대한 열쇠는 리소스 어댑터(Resource Adapter)에 있다. 리소스 어댑터는 이 어댑터를 사용하는 비즈니스 로직에 대해서 CCI(Common Client Interface)라고 불리는 공통적인 인터페이스를 제공한다. PurchaseOrder EJB에서는 CCI를 통해 단일한 로직으로 ERP, TP System을 바라볼 수 있게 된다. 애플리케이션 서버에서는 System Contracts라고 불리는 리소스 어댑터에서 정의되어 있는 기능을 이용해 어떠한 리소스 어댑터도 애플리케이션 서버에 플러그인되어 작동되어 질 수 있다. 물론 애플리케이션 서버내에서는 System Contracts를 지키면 트랜잭션도 같이 조절되게 된다. 이처럼 m x n 매트릭스의 인터페이스들이 1 x 1 인터페이스로 간략히 될 수 있다는 것이 커넥터 아키텍쳐를 통해 얻을 수 있는 장점인 것이다. 자 이제 CCI와 System Contracts에 대해 자세히 알아 보자.

 

CCI ( Common Client Interface)란 무엇인가?

 

우선 CCI는 EIS에 연결할 수 있는 Conneciton 기능, 특정 기능을 호출할 수 있는 Interaction 기능 그리고 특정 정보를 주고 받을 수 있는 데이터 표현 기능, 마지막으로 EIS의 리소스 어댑터의 정보를 얻을 수 있는 메터데이터 기능이 있다. 그림 5은 javax.resource.cci 패키지의 클래스 다이어그램이며 CCI의 기능들을 한 눈에 살펴 볼 수 있다.

 

[그림 5 CCI Class Diagram]

 

 

 

 

Connection 인터페이스는 EIS에 대한 커넥션을 총괄하게 된다. 인터액션(Interaction) 인터페이스는 EIS에 대한 특정 명령을 실행하게 해준다. 이때 필요한 인자들 즉 실행될 함수 이름, 수행모드(동기 혹은 비동기)같은 실행 정보들은 인터액션 스펙(InteractionSpec) 인터페이스를 통해 인터액션 인터페이스에 전달된다. Record 인터페이스는 EIS와 주고 받는 정보를 나타내게 해 준다. Record 인터페이스는 총 세가지 서브 인터페이스를 가지는데 이 세가지 서브 인터페이스를 통해 Map, List, Result의 형태로 EIS 정보가 표현된다.JDBC를 사용해 본 독자분들은 많은 추상화 아이디어가 JDBC와 유사한 형태를 띄고 있음을 알 수 있을 것이다.

 

System Contracts란 무엇인가?

 

EIS를 대리하는(delegation) 리소스 어댑터가 애플리케이션 서버내에서 동작하기 위해서는 애플리케이션 서버가 리소스 어댑터를 조절할 수 있는 어떤 표준이 존재해야 한다. 이 표준을 커넥터 아키텍쳐에서는 System Contracts라고 부르며 모든 EIS벤더가 리소스 어댑터를 제공할 때 CCI와 더불어 구현해야 하는 두번째 기능이 된다. 이 System Contracts에서는 다음의 세가지 기능을 정의하고 있다.

Connection Management
EIS에 대한 커넥션 풀을 유지하며 애플리케이션 컴포넌트로 하여금 EIS에 연결을 할 수 있게 해 준다.

Transaction Management
EIS에 대한 트랜잭션 조절을 가능하게 한다. 이것은 상이한 EIS들이 같은 트랜잭션내에서 조절될 수 있게 한다.

Security Management
EIS에 대한 접근(Access)을 조절한다.

 

커넥터 아키텍쳐와 디자인 패턴

 

커넥터 아키텍쳐는 다른 J2EE의 구성요소들과 마찬가지로 인터페이스에 대한 규약이다. 리소스 어댑터는 CCI와 System Contracts를 EIS에 맞게 구현해야 한다. 이 커넥터 아키텍쳐와 관계된 디자인 패턴에 대해 알아 보자.

 

추상 팩토리 패턴 ( Abstract Factory Pattern )

 

CCI의 Connection을 얻는 매커니즘이 바로 Abstract Factory 패턴을 따르고 있다. 비단 CCI뿐만 아니라 JDBC의 Connection 도  이 Abstract Factory 패턴을 가진다. 그림 6를 보자.

 

[그림 6 CCI의 Connection 매커니즘 Abstract Factory Pattern]

 

ConnectionFactory는 Connection을 얻는 통로 역할을 수행하는 인터페이스이며 보통 JNDI를 통해서 얻어진다. Factory 구현 클래스를 얻을 때는 얻고자 하는 클래스명을 명시한다. 각각의 Factory 구현 클래스(ConnectionFactoryImpl)는 getConnection() 메소드를 통해 각각에 맞는 ConnectionImpl을 돌려주게 된다. Abstract Factory Pattern입장에서 봤을 때  ConnectionFactory는 Abstract Factory 역할을 하며 Connection은 Abstract Product의 역할을 수행한다. 커넥터 아키텍쳐는 추상 팩토리 패턴을 이용함으로써 각기 상이한 Connection과 Connection Factory가 독립적인 벤더들에 의해 독립적으로 구현될 수 있게 한다. A와 B외에 C라는 벤더의 구현은 A, B의 구현에 영향을 미치지 않게 되는 것이다. 커넥터 아키텍쳐의 Connection 매커니즘에 사용된 추상 팩토리 패턴에서는 Abstract Product이 Connection 하나로만 존재하지만 여러 개의 Abstract Prodct이 하나의 Abstract Factory 구현에서 얻어질 수도 있다.

정리하면 추상 팩토리 패턴은 이처럼 여러 개의 Concrete Factory(위의 예에서는 ConnectionFactoryImplA와 ConnectionFactoryImplB) 와 여기서부터 얻어지는 Concrete Product들(ConnectionImplA와 ConnectionImplB)을 하나의 Abstract Factory와 Abstract Product로 사용될 수 있게 해준다.

 

브릿지 패턴 ( Bridge Pattern )

 

브릿지 패턴은 커넥터 아키텍쳐에서는 직접 쓰이고 있지는 않는다. 그러나 필자는 지금부터 커넥터 아키텍쳐의 인터페이스들이 어떻게 브릿지 패턴에 사용될 수 있는지 보일려고 한다. 우선 브릿지 패턴의 정의부터 살펴 보자. 브릿지 패턴의 클래스 다이어그램은 그림 7와 같다.

 

[그림 7 브릿지 패턴 클래스 다이어그램]

 

브릿지 패턴에서 중요한 관계는 바로 Abstraction 클래스와 Implementor 인터페이스의 관계이다. 이 둘의 관계가 브릿지(Bridge, 다리)와 같기 때문에 이 패턴의 이름이 브릿지 패턴이 되었다. Implementor는 정상적인 인터페이스이다. 이 Implementor를 구현하는 여러 구현클래스들( ConcreteImplementorA, ConcreteImplementorB )이 존재할 수 있다. Abstraction의 모든 행위들은 Implementor가 대리(delegation)하게 된다. 이렇게 되면 우리는 Implementor와 그 구현 클래스들에 상관없이 얼마든지 Abstraction을 Refine시켜 나갈 수 있게 된다. 그림 5에서처럼 RefinedAbstraction은 얼마든지 존재할 수 있다. 정상적인 상속관계에서는 RefinedAbstraction과 같은 클래스들이 존재할려면 구현클래스들이 변경이 되어야 했기 때문에 할일이 매우 많아진다.

 

이번에는 커넥터 아키텍쳐의 Connection인터페이스에 브릿지를 만들어 보자.( 그림 8)

 

[그림 8 브릿지 패턴을 사용해 커넥터 아키텍쳐상의 커넥션 매커니즘의 확장 ]

 

필자는 Connection에 대한 AbstractionConnection을 만들었고 이 AbstractionConnection클래스는 Connection에 대한 delegator역할을 한다. AbstractionConnection에서는 Connection의 여러 행위에 대한 로깅을 한다고 해 보자. 이후에 새로운 로깅 기능이 필요하게 된다면 이것은 ConnectionImpl에 대해서 영향을 주지 않고 단순히 AbstractionConnection들을 추가함으로써 상이한 로깅행위를 구현할 수 있게 된다.

 

java.awt도 브릿지 패턴의 좋은 예이다. Button, List같은 클래스들은 모두 Component라는 Abstraction을 상속하며 java.awt.peer에서는 Implementor역할을 하는 ButtonPeer, ListPeer, ComponentPeer를 제공하며 각 플랫폼에 의존적인 코드들이 존재한다. Button vs. ButtonPeer, List vs. ListPeer, Component vs. ComponentBeer는 모두 Abstraction과 Implemention의 관계에 있는 브릿지들이라고 할 수 있다.

 

정리하면 브릿지 패턴은 구체적인 구현에 대한 상속관계와 이 디테일한 구현에 의존하지 않는 Abstraction에 대한 상속 관계가 서로 분리되어져야 할 때 사용된다.

마무리의

지금까지 총 4회에 걸쳐 부족하나마 디자인패턴의 실제 적용사례들을 필자들의 경험에 비추어 정리하여 보았다. 이제 패턴의 소개를 마치고 이들을 정리하려 한다.

디자인 패턴들과 패턴의 적용

디자인 패턴이란 매우 광대한 분야이기에 이 짧은 연재에서 언급되지 않은 디자인 패턴의 수도 매우 많다. 연재를 정리하면서 이번 원고를 쓰는 데 중요한 정보원이 되었던 Design Patterns(Elements of Reusable Object-Oriented Software)에 설명되어 있는 모든 패턴을 한번 나열해 보기로 한다.

 

생성 패턴 ( Creational Pattern )

u       Abstract Factory

u       Builder

u       Factory Method

u       Prototype

u       Singleton

 

구조 패턴 ( Structural Pattern )

u       Adapter

u       Bridge

u       Composite

u       Decorator

u       Façade

u       Flyweight

u       Proxy

 

행위 패턴 ( Behavioral Pattern )

u       Chain of Resposibility

u       Command

u       Interpreter

u       Iterator

u       Mediator

u       Memento

u       Observer

u       State

u       Strategy

u       Template Method

u       Visitor

 

패턴을 실제에 적용한다는 것은 그렇게 쉬운 작업만은 아니다. 패턴들은 몇가지가 뒤섞여서 나타나기도 하고 적용되어야 하는 패턴들이 명확하지 않은 상황도 많이 벌어진다. 때로는 사용자 요구 사항이 변하여 애써 구현했던 패턴 자체가 변화하기도 한다. 예를 들어 Factory Method 패턴은 요구 사항이 좀 더 복잡해 지면 Abstract Factory 패턴으로 종종 바뀐다. 패턴은 책에서 나온 형태보다 간략화되어 쓰이는 경우가 많으므로 어떨 때는 과연 이것이 그 패턴인지에 대한 의심이 들 정도가 되기도 한다.

 

국내에서 디자인 패턴을 적용하는데의 어려움중의 하나는 국내 개발자들사이에서는 패턴이 널리 알려지지 않았다라는 점이다. 아마도 실제 프로젝트에서는 여러분들은 패턴이 어떻게 사용될지 고민하기 보다 다른 팀원들에게 디자인 패턴이 무엇을 의미하는지 설명하기 위해 더 많은 시간을 사용해야 할지도 모르겠다. 그러나 팀원들 모두에게 디자인 패턴에 대한 공감대가 형성되지 않은 상태에서 일부에 의해 도입된 디자인 패턴은 거의 의미가 없을 것이므로 디자인 패턴의 도입전에 스터디 같은 행위를 통해 팀원들간의 지식의 레벨을 맞추는 것이 무엇보다 중요하다. 소스에 이 클래스는 Singleton Pattern을 따른다고 커멘트를 달아 놓았는데 다른 팀원이 이 클래스에 잔뜩 static 메소드를 달아 놓는다면 어떻하겠는가?

 

디자인 패턴을 너무 복잡하게 생각하지는 말자. 본 연재의 처음에서도 언급했듯이 모든 디자인 패턴의 적용에는 다음의 세가지 원칙이 있다고 생각한다.  첫째는 언제나 인터페이스를 먼저 생각해 보라는 것이다. 그리고 인터페이스를 적극적으로 사용해야 한다. 그러나 유틸리티 같은 클래스에서조차 인터페이스를 사용할 필요는 없다. 인터페이스는 클래스들간의 커플링을 최소화시켜 줄 것이다. 두번째는 상속(Inheritance)보다는 Delegation을 주로 사용하라는 것이다. Delegation은 상속에서처럼 정적인 관계가 아니라 동적인 상속관계를 만들어 주기 때문에 보다 유연한 관계를 만들어 줄 수 있다.  사실 많은 디자인 패턴들은 인터페이스와 Delegation, 그리고 상속을 기본 요소로 하고 있다. 셋째는 항상 Coupling을 최소화하는 데 역점을 두어야 한다. 만약 OO 프로젝트에서 C에서처럼 로직을 한 펑션에 주욱 기술하고 있다면 그것은 어딘가 잘 못된 것이다. 새로운 클래스와 인터페이스를 도입하고 delegation과 inheritance를 사용해서 클래스간의 관계를 설정하자. 언제나 요구 사항은 변화하고 기능 또한 변화하게 된다. 이러한 변화가 시스템 전체적으로 영향을 주지 않는 구조를 만드는 것은 모든 디자인 패턴의 목적중에 하나이다.

 

 Design Pattern과 소프트웨어의 제작

 

객체 지향 프로그래밍의 대가 중 한명으로 알려진 Grady Booch의 글 중에서 소프트웨어를 건축물에 비교한 부분이 있다.

 

여러분들이 건축물을 만드는 건축가로써 활약하고 있다고 생각해보자. 여러분의 집이나 창고 같은 조그마한 건축물을 지었을 때, 그리고 조금 더 큰 건축물을 지었을 때, 나아가서 초대형 복합 건물을 건축하여야 하는 경우, 어떻게 하겠는가?

 

자신이 직접 망치와 톱을 들고, 나무를 썰어가면서 집을 지을 수 있을까? 결코 아니다. 오직 고양이나 개와 같은 작은 동물의 집만이 이러한 방식으로 지을 수 있다. 여러분들이 살아갈 조그마한 오두막을 건축하는 것에도 문과 방과 창문들을 만들고, 나무들을 적절히 자르고 운송하고 얽어 묶는 절차가 필요하다.

 

이제 초대형 주상 복합 건물을 여러분이 건축하게 되었다. 그럼 톱과 망치를 들고 나무 판자에 못 박는 것에서 건축을 시작하겠는가? 결코 그러한 방식으로는 건축할 수 없다.

 

굳이 초대형 주상 복합 건물과 같은 구조가 복잡한 건출물이 아닌, 가정집과 같은 자그마한 건축 과정에 있어서도 가장 필요한 것은 설계이다. 건축물을 어떻게 만들 것인가? 에 관한 설계가 탄생되고서부터, 벽돌을 쌓고, 하나씩 구성요소들을 만족시켜 나야 한다. 패턴은 건축물을 구성하는 구성요소이다. 소프트웨어 건축의 도구이며, 재료이다. 이들이 가져오는 영향들에 비하여 구성 원가가 저렴한 재료들이다.

 

반면 패턴들을 도입하여 나타나는 영향은 매우 크다. 필자들의 경우, 패턴을 도입하고 난 뒤부터 제작되는 소스 코드의 수준이 상당 수준으로 향상 되었음을 깨닫고 있다. 모든 부분에 패턴들이 사용되었기 때문이 아니라, 패턴을 공부하는 과정에서 함께 깨달았던 객체 지향의 여러 개념들이 소스 코드에 반영되기 때문이다. 여기에는 구현과 인터페이스의 분리, 페케이드(Façade) 클래스로 복합적인 여러 기능을 하나의 클래스로 통합하는 과정 등이 포함된다.

가끔씩 패턴과 이들 개념을 이용하여 3~4일정도 프로그램을 작정하고 컴파일하고 작동시킬 때, 오류가 거의 발생하지 않는다는 사실을 발견하는 것은 큰 기쁨이었다.

 

필자들이 객체 지향 프로그래밍을 공부하면서 더욱 깨닫는 것은 패턴의 소중함이다. 객체 지향 소프트웨어 개발 라이프 사이클이서는 이터레이션이라는 과정이 있다. 결과물을 완성하기 위해서 몇 단계의 이터레이션을 반복하여 소프트웨어를 완성하는 것이 객체 지향 개발 방법론에서 각광 받고 있다. 예를 들어 5개월에 걸쳐서 소프르웨어를 완성한다고 가정하여 보자. 1개월 설계, 1개월 1차 제작, 1개월 2차 제작, 1개월 3차 제작, 1개월 테스트의 과정으로 3번의 제작 과정을 반복하는 것이 이터레이션이다. 디자인 패턴은 각각의 이터레이션에서 소프트웨어 구성 요소들을 재활용하는 단위로 사용된다. 각각의 이터레이션의 목표 설정에 관여하는 구성요소들이다.

 

가끔씩, 객체가 아닌 오라클이나 MS-SQL같은 RDB는 어떻게 표현해야 합니까?라는 질문을 듣는다. 여기에 대해서 모든 것이 객체라는 설명을 하고 싶다. 관계형 데이터 베이스의 테이블도 하나의 객체이며,이를 대표하는 클래스로 표현될 수 있다. 따라서 객체 지향의 프레임 워크에서는 모든 것이 객체로 표현된다. 관계형 데이터 베이스도 OR MAPPING의 과정을 거쳐서 객체로 표현되어, 객체 단위로 설계된다. 디자인 패턴은 이 객체 프레임 워크에서 객체들의 동작과 행위를 규정하는 중요한 역할을 담당한다. 어떤 객체들이 다른 객체에 접근할 때의 접근하는 행동 양식을 기술한 규범의 역할을 담당한다.

 

무엇이 기술인가?

진정한 소프트웨어의 기술은 어디에서 나타나는가? Windows2000, Linux, Netscape등의 시스템을 만들어가고 구현하는 것이 바로 기술일까?

소니의 기술을 보라, IBM의 기술을 보라. 간결하고 깔끔하면서도 편리함을 추구하는 소니의 제품들과 JAVA를 기반으로 엔터 프라이즈를 구성하는 일련의 제품군들이 하나의 개념으로 연결되는 IBM의 기술은  보고 있으면 절로 탄사를 자아내게 만든다.

마이크로 소프트의COM 객체도 초창기에는 OLE ( Object Linking & Embedding ) 이라는 복잡하고 사용하기 힘든 기술이었으나, 시간이 지나면서 COM, ActiveX 로 더욱 간결한 개념과 사용하기 편리한 프로그래밍 수단으로 발전되어 같다.

분명히 이야기 하건데 복잡한 기술 요소들이 덕지 덕지 붙여 적절한 상호 연동 없이 복잡하기만 한 것은 결코 진정한 기술이 아니다. 진정한 기술은 간단함에서 부터 시작된다.

소프트웨어 설계자나 소프트웨어의 코드를 만드는 개발자들에게 기술은 설계와 개발을 간단한 컨셉으로 충분한 기능을 지원하게 만드는 것이다.

이러한 설계에서 간단한 것이 기술이다. 같은 기능을 수행하는 소프트웨어를 설계 하였으나, 한쪽이 다른 한쪽보다 설계가 복잡하고 구현하기 힘들다면 개발자는 어느쪽을 선택할 것인가?

설계에 있어서 간단하고도 강력한 개념에 기반을 둔, 확장성 있는 설계, 미리 검증된 소프트웨어의 설계를 위해서 디자인 패턴은 사용되어야 한다. 건축가가 건물을 지을 때, 방/거실/회의실/화장실등의 여러 개념들을 활용하여 건물을 구성한다. 개발자는 소프트웨어를 설계할 때, Proxy, Factory, Bridge등의 여러 개념들을 활용하여 소프트웨어를 설계한다. 이들  기본이 되는 개념이 바로 지금까지 설명해온 디자인 패턴들이다.

많은 경우, 사람들은 오해한다. 복잡하고 어렵게 만드는 것이 어렵다고. 그러나, 더욱 어려운 것은 복잡한 시스템을 간결하게 구성하는 것이다. 좋은 시스템은 간결하고 접근하기 쉬운 개념으로 만들어 진다.

복잡하게 구성된 시스템이 정확하게 원하는 바대로 동작하려면, 그 핵심 구성 요소들이 간단하고 명백한 방식으로 정의되어 있어, 동작 원리에 따른 시스템의 행동이 파악되어야 한다. 복잡하고 어려운 구성요소가 편하게 사용될 수 있도록 진정 쉽게 설계되어야 한다. 간단하면서도 다양한 기능을 가지는 소프트웨어를 만드는 것인 쉽지 않다.

여러분들 중에는 소프트웨어의 제작을 단순한 기능이라 생각하시는 분들도 있을 것이다. 하지만, 대부분의 프로그래머들은 자신의 코드들에 자존심을 걸고 있다. ( 주 : 그래서 코드 리뷰를 그렇게 싫어하는지도 모른다.^^ ) 프로그래머가 작성한 소스 코드는 그 사람의 얼굴이자, 그 사람의 실력을 그대로 표현한다. 프로그래머에게 코드는 단순한 기술이 아닌 자신을 표현하는 수단이자 도구이다. 나름대로의 프로그래밍 철학과 노하우를 지닌 사람에게는 코딩은 기술이 아닌 예술로써 나타난다. 좋은 코드를 만드는 사람은 예술가의 정신, 장인의 정신을 가지고 나름대로의 세계를 탐미하여 새로운 작품을 창조한다. 코드는 그것에서부터 시작된다.

간단하고 단순명쾌하면서도 좋은 프로그램을 만들었을 때, 분명 희열을 느낄 것이다. 그렇다면 어떻게 간단하고 단순명쾌하면서도 좋은 프로그램을 만들 수 있을까? 디자인 패턴은 많은 프로그래밍의 선배님들이 자신들의 노하우를 후대에게 전수하고자 만들어 놓은 개발 기법이다. 여기에는 어떻게 하면 소프트웨어를 간단하면서도 단순 명쾌하게 만들 수 있는가?에 관한 명백한 방법이 포함되어 있다. 물론 디자인 패턴은 소프트웨어 제작의 전부는 아니며 일부에 불과하다. 하지만, 이 일부가 매우 중요한 일부라는 사실을 명심하기 바란다.

국내 개발자들에게 하고 싶은 말.

 

필자는 1994년부터 사용되기 시작한 디자인 패턴이 국내에는 왜 유독 잘 알려지지 않았을까? 하는 의구심을 가지고 있었다. 필자가 추정한 사유로는

1.       디자인 패턴 책과 기타 자료는 경험 있는 프로그래머가 아니면 숙지하기 힘들다.

2.       디자인 패턴 책은 영어가 어렵다.

2가지 주요한 요인이 있었으리라고 추정된다. 디자인 패턴은 프로그래머의 경험이 부족하면 이를 숙지하기 힘들다. 단순하게 머리 속으로 생각하는 것과 경험에 의거하여 패턴의 사상을 확연하게 깨닫는 것과는 많은 차이가 있다. 여기에 디자인 패턴 책이나 다른 객체 지향과 컴포넌트 제작 분야에서 널리 알려진 책들은 하나같이 영어가 매우 어렵다. 작가들의 소프트웨어의 철학을 담고 있기 때문일 것이다. 기회가 닿는다면 해외에서 경력과 어학 실력을 쌓아두는 것도 많은 도움이 될 것이다.

마지막으로.

 

독자 분들이 진행하고 있는 모든 프로젝트에 디자인 패턴을 고려해 보라. 현재 진행되고 있는 프로젝트가 있다면 디자인 패턴을 사용해 Refactoring을 해보고 완전히 새로운 프로젝트라면 클래스 디자인단계에서 디자인 패턴을 최대한 많이 사용해 보도록 하자. 잘 이해가 안되던 패턴들도 구현을 하면서 이해가 되기도 하고 동시에 패턴이 적용되면서 알고리즘이 더 깨끗하게 정리되는 것을 느낄 수 있을 것이다. 필자들이 느꼈던 많은 놀라움들을 독자 여러분들도 함께 공유하게 되기를 바라면서 본 연재를 마치겠다.

 

참고 문헌

1.       Patterns in Java Volume 1, Mark Grand, Wiley Press,1999

2.       Design Patterns, Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1995

3.       JavaSpaces Principles,Patterns, and Practice, Freeman, Hupfer, Arnold from Sun Microsystems.1999

4.     Design Patterns by Contracts, Jean-Marc Jezequel, Addison-Wesley,2000

5.       JINI 1.0 Source Code, Sun Microsoft Systems

6.       JDK 1.2.2 Source Code, Sun Microsoft Systems

7.     J2EE Connector Architecture Public Draft

8.     J2EE Connector Architecture API Document

 

박스기사 1. Adaptor 패턴

어댑터 패턴

어뎁터 클래스는 인터페이스를 구현을 통하여 클라이언트에 알려지지 않은 클래스 인스턴스를 억세스 가능하게 한다. 어뎁터 객체는 어떠한 객체가 인터페이스를 구현하였나에 관계 없이 인터페이스에 의해 정의된 기능을 제공한다. from Patterns in JAVA

 

간단히 말하자면 객체를 인터페이스와 구현 클래스로 분리하여 여러 개의 객체들이 하나의 인터페이스에 의해서 사용될 수 있도록 정의하는 것이다. 왜 이렇게 하는 것일까? 프로그램들을 재사용하기 위한 방법 중의 하나는 인터페이스와 구현 클래스의 분리이다. 기능을 이용하기 원하는 클라이언트 클래스들은 오직 인터페이스만을 사용한다.

 인터페이스와 구현 클래스의 분리는 최상의 캡슐화 효과를 가져온다. 단지 인터페이스만을 사용하는 클라이언트 클래스에서는 인터페이스를 구현한 구현 클래스의 내부적인 상태에 접근할 방법이 없다.

 

3.       그림 - Adapter Pattern

 

위의 그림에서 어뎁터는 인터페이스를 제공하여 외부에 노출시킨다. 정작 어뎁터 클래스 자신은 작업을 수행하기 위해서 어뎁티(Adaptee) 클래스를 사용한다. 이 부분은 어뎁터 클래스가 단순한 클래스 인터페이스 분리와 다르다는 것을 암시한다. 어뎁터 클래스에는 구현이 거의 존재하지 않으며 이름 그대로의 어뎁터 역할을 담당한다. 어뎁터는 클라이언트 클래스에 기능을 제공하기 위한 중간 클래스의 역할을 담당한다.

 

결과적으로는 기능을 구현한 어뎁티 클래스와 기능을 사용하는 클라이언트 클래스는 상호 독립적으로 존재하게 된다. 그리고 어뎁터 클래스를 이용하여 어떤 메서드가 호출될지를 다이나믹하게 설정할 수 있다. 데이터 베이스 데이터를 가지고 오는 클래스가 구현되어 있고 이를 어뎁터를 사용하여 이용하였을 때, 데이터 베이스가 아닌 XML 과 같은 다른 데이터 소스에서 정보를 가져올 때도 클라이언트가 어뎁터만 변경하면 동일한 인터페이스로 접근할 수 있다.

 



[1] JavaSpace.notify  EventListener.notify 역할이 명확히 다르다는 사실을 염두에 두기를 바란다. 전자는 이벤트 등록 과정이며 후자는 이벤트 처리과정이다.

'SE' 카테고리의 다른 글

UML[2/4]  (0) 2008.07.25
UML[1/4]  (0) 2008.07.25
Pattern[3/4]  (0) 2008.07.25
Pattern[2/4]  (0) 2008.07.25
Pattern[1/4]  (0) 2008.07.25
And