소프트웨어 모듈을 작성하는 기본적인 규칙 중 하나는, 컴포넌트 간의 커플링을 없애는 것이다. 컴포넌트간의 연결이 너무 강력하면, 시스템을 변경하지 않고, 일부 컴포넌트를 변경하거나, 설정을 변경하는 것이 어려워진다. 

이클립스에서 모듈간의 커플링을 제거하기 위해 익스텐젼 포인트익스텐젼이 사용된다. 익스텐젼 포인트의 가장 간단한 비유는 플러그이다. 

우리는 전기 콘센트에 맥북이나, 냉장고등을 연결하여 전기를 사용하여 다양한 일을 할 수 있다. 또 PC의 USB포트에 여러 주변기기를 연결하여 PC의 기능을 확장할 수 있으며, 아이폰의 유니버셜 포트에 독을 연결하여 음악을 감상하거나, DMB모듈을 연결하여 TV를 시청할 수 있다. 이 때 이 컴포넌트들이 연결되는 통로익스텐젼 포인트라고 하며, 그 연결을 통해 연결된 각각의 컴포넌트익스텐젼이라고 한다. 이러한 익스텐젼 포인트와, 익스텐젼들은 플러그인이라고 부르는 단위에 실리게 된다.

전기 콘센트에 연결된 컴퓨터가 USB포트를 갖는 것 처럼, 한 익스텐젼을 소유한 플러그인은 스스로 익스텐젼 포인트를 가질 수도 있다. 

플러그인익스텐젼 포인트를 통해 그 기능의 일부를 확장하거나, 커스터마이즈 하는 것을 허용한다. 익스텐젼 포인트는 단순한 XML 구조 기술 파일로, 익스텐젼을 공급할 때 따라야 하는 규칙들에 대한 정보를 갖고 있으며 XSD로 정의 된다. 이 정보는 익스텐젼 공급자가 구현해야할 자바 인터페이스등을 포함하기도 한다. 

익스텐젼 공급자는 이 명세서 이외에, 익스텐젼 포인트를 제공한 컴포넌트에 대해서 아무것도 알 필요가 없기 때문에, 서로에 대한 지식 없이도, 개인이나 회사에의해 만들어진 플러그인들이 서로 조화를 이루며 이클립스 플랫폼 위에서 동작할 수 있다. 

'Eclipse Core' 카테고리의 다른 글

P2의 주요 용어 정리  (0) 2010.11.29
아아 좋은 이클립스 삽질이다  (0) 2010.11.01
어댑터 디커플링  (2) 2009.08.10
Job과 Schedule Rule  (0) 2009.08.08
Posted by 지이이이율
,

한 컴포넌트가 다른 컴포넌트와 닿는 접점으로 아주 흔하게 사용되는 것 중 하나가 바로 리스너 패턴입니다. 커플링도 적은 편이고, 이해하기도 쉬운 편이지요. 이클립스 같이 전사적인 블라인드 협력이 이뤄지는 구조에서는, 이러한 접점들을 안전하게 관리하는 것이 매우 중요합니다.

원칙1. 당신의 리스너가 완벽할 것이라는 기대를 버리시라.

그렇습니다. 우리는 인간이기 때문에, 거의 반드시라고 해도 좋을 정도로 실수를 하곤 합니다. 대부분의 실수는 지역적인 장해만을 발생시키지만, 컴포넌트의 접점에 문제가 발생할 경우, 그 지점과 연관된 모든 부분이 엉망이 되어 버릴 수 있으며, 심각 한 경우 사용자가 작업을 더 이상 진행 할 수 없게 되어버리기도 합니다. 그러므로 컴포넌트 접점은 좀 더 각별한 주의를 기울여 보호할 필요가 있습니다.

for(int i=0; i<100; i++){
	doSomething(i);
}

위의 코드를 예로 들어 봅시다. 만약 doSomething이라는 메서드가 i가 17일 때 문제를 일으켜 익셉션을 던진다고 가정 해 봅시다. 이 경우 정상적인 작업 83개가 17번 작업 때문에 모두 수행되어버리지 않게 됩니다.

이벤트를 제공하는 컴포넌트들은 여러개의 리스너를 가지고 있습니다. 만약 한 리스너가 문제를 일으킨다면, 아직 이벤트를 공급 받지 못한 나머지 모든 리스너들은 제대로 반응하지 못할 것이고, 이 후 같은 이벤트 통지역시 모두 마비 되어버리고 맙니다. 이 문제를 해결하기 위해서, 저는 불신과 배척이라는 테크닉을 이용합니다.

불신과 배척

불신과 배척의 철학은 아주 간단합니다. 기본적으로 리스너를 믿지 않으며, 문제를 일으킨 놈은 퇴출시켜 버립니다. 자 그 구현을 볼까요?

private Set<Listener> listeners;

private void fireEvent(Event e){
  Listener[] listenerArray = listeners.toArray(new Listener[listeners.size()]);

  for(Listener eachListener : listenerArray){
    try{
      eachListener.eventOccured(e);
    catch(Exception e){
      listeners.remove(eachListener);
      debug(eachListener + " 말썽쟁이를 쫒아냈어요.");
    }
  }
}

자 이제 한 줄 씩 살펴 볼까요? 우선 리스너 세트를 바로 이터레이트 하지 않고, 배열에 담은 다음 이터레이션 했습니다. 그 이유는 간단 합니다. 만약 리스너가 이 이벤트로 인해, 자기 자신을 리스너 그룹에서 제외하거나, 다른 리스너를 참가시킬 경우, 컨커런시 문제가 발생하기 때문이죠. 이미 for 문이 참조중인 컬렉션이 for 문이 도는 도중 변경 되어버리면 for 문은 어쩔 줄 몰라하게 되겠죠?

다음은 이터레이션 내부를 봅시다. try 로 모든 개별 리스너에게 통지할 때마다 문제를 감지하는 군요. 그렇다면 한 리스너가 문제를 일으킨다 하더라도, 다른 리스너들이 피해를 입지 않겠네요. 게다가 문제를 일으킨 녀석은 리스너 그룹에서 빼 버리는 군요. 저는 그저 추방했지만, 몇 번 더 기회를 줘 본다던지와 같은 재미있는 대응도 가능하겠죠?

장해 복구

그런데 이벤트에 대응하는 리스너가 UI를 변경하는 일은 흔히 있는 일입니다. 리스너의 개발자는 자기 자신이 UI 스레드 내에서만 호출될 거라며 바보같은 고집을 피우고 있을 지도 모릅니다. 하지만 정말 예상하지 못하게도, 다른 스레드에서 이벤트가 발생하는 경우는 종종 있습니다. 이렇게 흔히 발생할 수 있는 오류에 대해서는 다음과 같은 대응도 시도 해 볼 수 있습니다. 만약 발생한 예외의 종류가 InvalidThreadAccessException 이라면, UI 스레드에 동기화 하여, 이벤트를 한 번 더 전달 해 줘 보는 것이지요. 물론, 상태가 중복적으로 수정된다던가 하는 문제가 발생할 수도 있습니다. 도전과 실험은 여러분의 몫입니다.

결론

이클립스는 수많은 블라인드 협력을 통해 운영되는 독특한 공간 입니다. 자신이 실수를 하지 않았다고 해서, 자신의 클라이언트가 실수를 했다고 해서, 마비되는 컴포넌트를 작성해서는 결코 안되며, 좋은 이클립스 개발자가 되려면 이러한 경우 자신의 잘못으로 인정할 수 있는 반성이 필요합니다. 엔드유저가 격게될 피해를 최소화하는 것은 무엇보다 중요한 일입니다. 자신의 코드를 잘 만드는 것도 중요하지만, 협력의 준비가 되지 않은 코드는 아무리 우수하더라도 많은 이의 골칫거리가 될 뿐입니다.

Posted by 지이이이율
,

어댑터 패턴

어댑터 패턴의 기본 전략 중 하나는, 한 객체가 특정한 영역에서만 의미를 갖는 기능집합을 요구 받을 때, 원래의 객체로 부터 분리된 어댑터 객체에게 그 임무들을 위임하는 것입니다. 이렇게 함으로써, 객체는 본래의 간결함을 유지 할 수 있으며, 개발자는 본인이 개발하지 않은 컴포넌트에 대해서도, 다른 영역에 참가시키는 것이 가능합니다.

기능영역 -> 컴포넌트: getAdapter();
컴포넌트 -> 컴포넌트: 어댑터 생성
컴포넌트 --> 기능영역: adapter
기능영역 -> 기능영역: 쓰임새 수행
어댑터블한 컴포넌트가 기능영역에 참여하는 기본 원리

일반적으로 어댑터 질의는 그림처럼 IAdaptable 인터페이스의 getAdapter(Class) 메소드를 통하여 이루어집니다. 하지만, 이 경우에, 어댑터 질의에 응답하는 주체가 객체 그 자체가 되기 때문에, 어댑터가 추가되거나 변경될 때 마다, 객체의 코드가 변경되어야 합니다. 이 경우엔 유연한 협력이나, 블라인드 확장(원래의 컴포넌트가 어떻게 구현되었는지 전혀 모르는 상태에서 기능을 확장시키는 것)이 불가능 합니다. 그러나 서로 모르는 사람들이 가장 많이 참여하는 프로젝트인 이클립스에서는, 이러한 상황이 매우 빈번하게 일어납니다. 이 문서는 모델과 어댑터간의 디커플링을 달성 하는 방법과 원리를 설명합니다.

어댑터 질의를 플랫폼에 위임

이런 문제를 해결하기 위해서 이클립스에서는 어댑터 질의를 플랫폼에게 위임하는 전략을 사용합니다.

기능영역 -> 컴포넌트: getAdapter()
컴포넌트 -> 어댑터매니저: 위임
어댑터매니저 -> 확장점: 팩토리 찾기
확장점 -> 확장점: 확장 탐색
확장점 -> 어댑터팩토리 : 생성
어댑터팩토리 -> 어댑터팩토리: 어댑터 생성
어댑터팩토리 --> 컴포넌트: adapter
컴포넌트 --> 기능영역: 쿼리 응답
어댑터블한 컴포넌트가 기능영역에 참여하는 기본 원리

이클립스에서 어댑터블한 객체들은 다음과 같은 기본 구현을 가지고 있습니다.

public Object getAdapter(Class adapterType){
	...
	return Platform.getAdapterManager().getAdpater(this, adapterType);
}

두 번 째 줄에서 보이는 바와 같이 이 객체는 어댑터 질의를 Platform에게 위임시킵니다. 따라서, 개발자는 플랫폼에 어댑터 공급 방법을 알려줌으로써, 원래 객체를 수정하지 않고도 어댑터를 추가 공급할 수 있습니다. 이렇게 어댑터를 추가적으로 공급하는데 쓰이는 확장점이 바로 런타임 어댑터 (확장점ID: org.eclipse.core.runtime.adapters) 입니다. 이 확장점은 특정한 타입의 객체에 대해, IAdapterFactory를 등록할 수 있게 해 줍니다. 마찬가지로 여러분이 만든 컴포넌트도 런타임 어댑터를 지원하려면, getAdapter()의 마지막 부분에 마찬가지의 코드를 삽입해야 합니다.

어댑터 팩토리

public interface IAdapterFactory {
	public Object getAdapter(Object adaptableObject, Class adapterType);
	public Class[] getAdapterList();
}

어댑터 팩토리는 위와 같은 메소드 구성을 가집니다. getAdapterList()는 이 어댑터 팩토리가 어떠한 종류의 어댑터 질의를 처리 할 수 있는지를 나타내고, getAdapter()는 실제로 어댑터를 만들어 플랫폼에 제출하는 역할을 합니다. 확장점 정의에보면, 이미 가능한 어댑터 종류에 대한 노드들이 있는데도 불구하고 구현 클래스도 질의를 받는 인터페이스가 있는 이유는, 확장점 뿐만 아니라, 프로그래밍으로도 어댑터를 등록할 수 있게 하기 위해서 입니다.

정리

직접 작성한 객체나 컴포넌트가 아니라고 하더라도, org.eclipse.core.runtime.adapters 확장점을 이용하면 특정 도메인에 대한 어댑터를 별도로 공급할 수 있습니다.

하는 김에 같이 줏어먹기

Platform.getAdatper(...) 는 현재 활성화된 플러그인들 중에서만 런타임 어댑터 팩토리를 찾아내어 작동합니다. 만약 모든 플러그인과 연동되게 하려면, loadAdpater(...)를 사용해야 합니다. 이 경우, 어댑터 질의 과정중에 다른 플러그인이 활성화 될 수도 있다는 점을 염두에 둬야합니다.

Core Expression

만약 코어 익스프레션에서 adapter 노드를 사용한 경우에, 런타임 어댑터로 등록된 어댑터들만 리턴값이 넘어옵니다. 주의하세요.

'Eclipse Core' 카테고리의 다른 글

P2의 주요 용어 정리  (0) 2010.11.29
아아 좋은 이클립스 삽질이다  (0) 2010.11.01
Extension Point and Extension  (0) 2010.07.08
Job과 Schedule Rule  (0) 2009.08.08
Posted by 지이이이율
,