'중재자'에 해당되는 글 1건

  1. 2007/09/04 미디에이터 (Mediator) 패턴 : 중재자 (1)
Languages/Design Pattern2007/09/04 10:54
Mediator 패턴 UML 다이어그램

Mediator 패턴의 UML 다이어그램


오늘 살펴볼 패턴은 Mediator 패턴입니다. 이 패턴은 UML 다이어그램만으로는 그 의미가 명확하게 드러나지 않는 패턴 중 하나입니다. 보시면 아시겠지만 좌측 상단에 Mediator가 있고, 그 인터페이스를 구현하여 만들어지는 ConcreteMediator가 좌측 하단에 자리합니다. 우측 상단에는 Colleague 인터페이스가 있고, 우측 하단에는 역시 그 인터페이스를 구현하여 만들어지는 다양한 Concrete 클래스들이 있지요. ConcreteMediator는 이들 ConcreteColleague들과 association 관계를 맺고 있는데, UML 다이어그램만 봐서는 이들 association이 정확하게 무엇 때문에 필요한지가 잘 드러나지 않습니다.

사실 Mediator 패턴은 클래스 간 인터페이스의 복잡도를 떨어뜨릴 목적으로 고안된 패턴입니다. N 개의 객체가 있고, 어떤 작업을 성취하기 위해서 이들 N 개의 객체들이 서로 협업할 필요가 있다고 해 봅시다. Mediator 패턴을 사용하지 않는 경우 이들 N개의 객체들이 서로 협업하도록 만들려면, 최악의 경우 N(N-1) 개의 객체간 링크가 필요할 것입니다. (Mesh 구조를 생각해 보시면 아마 이해가 쉬우실 것입니다.)

Mediator 패턴은 이런 N(N-1)의 복잡도를 떨어뜨리는 것이 목적입니다. Mediator객체를 중간에 놓고, 협업이 필요한 객체들이 Mediator를 통해 통신하도록 함으로써 N(N-1)의 링크 복잡도를 N의 수준으로 낮추는 것이 Mediator 패턴의 목적이죠. 만일 N 개의 클래스가 있는데, 이들 클래스에 의해 만들어진 객체들이 하나의 Mediator 객체와 통신하여 상호 연동할 수 있다면, 결국 객체간 링크의 개수는 N개로 감소하게 될 것입니다.
자. 그렇다면 Mediator 패턴을 코드로 옮길 때에는 과연 어떻게 하면 될까요? 앞서도 잠시 언급했지만, Mediator 패턴의 문제는 UML 다이어그램만 봐서는 대체 어떤 식으로 코드를 구현해야 할지가 잘 드러나지 않는다는 것입니다.

하지만 한가지 힌트는 있습니다. ConcreteMediator 객체를 보면 다른 ConcreteColleague 객체들에 대한 Association을 가지고 있으니, 적어도 Mediator 객체 안에 다른 참여 객체들을 참조할 수 있는 정보(C++의 경우에는 포인터나 레퍼런스, 자바의 경우에는 레퍼런스)는 있어야 한다는 것을 알 수 있겠죠.

그런데 그 정도 정보 말고 다른 정보가 있나요? 없습니다. -_-; 따라서 Mediator 패턴을 실제로 구현하는 것은 구현시 주어지는 요구사항에 크게 좌우됩니다. 우선, 다음과 같은 요구사항이 주어졌다고 가정하고, 그 문제를 Mediator 패턴을 통해 풀어보도록 하죠.

공장 내에 설치된 장비실 온도를 감지하는 센서가 있습니다. 이 센서로부터 수집된 정보는 관리 목적을 위해 DBMS에 계속해서 반영됩니다. (그래프 등을 그린다거나 할 때 유용합니다.) 그런데, 이 정보는 DBMS에 저장될 뿐 아니라, 공장 관리자들이 들고 다니는 단말 장치 (PDA와 Cell Phone 두 가지 종류가 있다고 합시다) 에도 전송되어야 합니다. 온도가 지정된 온도 이상으로 넘어가면, 단말 장치에는 위험 경고 메시지가 표시되어야 하고, 장비실의 여벌 에어컨이 추가로 가동되어야 하고, 장비의 가동 속도는 잠정적으로 늦추어져야 합니다. 관리자들은 단말 상에서 그런 상황들을 보고 장비를 끈다거나, 에어컨을 끄거나 켜는 등의 추가적인 작업을 수행할 수 있어야 합니다.
Mediator가 없는 상황에서 위의 요구사항을 만족시키기 위한 프로그램을 짠다고 생각해보죠. 일단 장비가 있고, 에어컨이 있고, 센서가 있습니다. 장비실에 있는 이 3종의 장치 사이에도 우선 인터페이스가 있어야 합니다. 센서는 DBMS와도 연동해야 하고, 관리자들이 들고다니는 단말들과도 연동해야합니다. 단말들은 DBMS와 연동해야 할 뿐 아니라, 에어컨이나 장비들과도 연동해야 합니다.

복잡하죠 -_-;;; 굳이 다이어그램이 없어도, 클래스 다이어그램 상에 그려지는 복잡한 링크들이 떠올라 마음이 답답해집니다. 그런 복잡한 링크들은 그냥 단순한 시뮬레이션용 프로그램상에서는 적절히 기능할지 몰라도, 실제 상황에서는 거의 무용지물입니다. 단말 안에 DBMS 연동, 에어컨 연동, 장비 연동을 위한 복잡한 프로토콜들을 다 우겨넣을 바보는 없지 않겠어요? 실제 상황이라면, 장비는 중앙의 누군가와만 통신해야 하고, 센서도 중앙의 누군가와만 통신해야 하고, 실제 제어는 그 '중앙의 누군가'가 전담해야 합니다. 그래야 단순히 Simulation 용도에 머무르지 않는, 좀 더 실제 상황에 근접한 클래스 다이어그램이 만들어질 수 있습니다.

자. 그렇다면 지금부터 Mediator 패턴을 준수하면서, 위의 요구사항을 만족시키는 클래스들을 설계해 보도록 합시다.

우선, Mediator 인터페이스가 필요할것 같군요.

interface Mediator {
}

그런데, 저기까지를 쳐 놓고 생각해보면, 과연 Mediator 패턴을 구현하는 데 있어 Mediator 인터페이스라는 것이 어떤 역할을 해야 하느냐가 좀 궁금해집니다. 인터페이스라는 것은 그 인터페이스를 준수하는 여러 객체들이 다른 객체들과 상호작용해야 하는 경우가 빈번할 때 필요하잖아요? 그런데 위의 시나리오를 처리하는 데는 단 하나의 Mediator 객체만 있으면 되는데다, Mediator 인터페이스가 있다고 해서 그 인터페이스가 쉽게 재사용될 것 같지는 않아요. 그렇다면, 단도직입적으로 Mediator를 클래스로 구현하는 편이 더 낫지 않을까요?

이 질문에 No라고 대답하신 분들은, 아마 코드를 다음과 같이 구성하실 분들일 겁니다.

enum NodeType { SENSOR, DBMS, GUI };

interface Mediator;

interface Colleague {
    public NodeType getNodeType();
    public void receiveNotificationFromMediator(String notification);
    public void registerMediator(Mediator medi);
}

class DBMSColleague implements Colleague {
    private Mediator med;

    public DBMSColleague() {
        med = null;
    }

    public NodeType getNodeType() {
        return NodeType.DBMS;
    }

    public void receiveNotificationFromMediator(String notification) {
        ... // 일단 생략
    }

    public void registerMediator(Mediator m) {
        med = m;
    }

    ... // 이하 일단 생략
   
}

interface Mediaitor {
    public void registerColleague(NodeType type, Colleague c);
    public void receiveNotificationFromColleague(NodeType type, String msg);
}

class FactoryMediator implements Mediator {
    public void registerColleague(NodeType type, Colleague c) {
        if ( type == NodeType.SENSOR ) {
            ....
       }
       // 이하 일단 생략
    }

    public void receiveNotificationFromColleague(NodeType type, String msg) {
       if ( type == NodeType.SENSOR ) {
            ....
       }
       // 이하 일단 생략
    }
}

아마 이런 식으로 구현이 진행되는걸 염두에 두셨을 겁니다. 그런데 이런 코드 어디서 많이 본 코드 아닌가요? 네, 맞습니다. Observer 패턴을 구현할 때 많이들 짜게 되는 코드죠. 사실 Observer 패턴과 Mediator 패턴은 여러가지로 비슷합니다. Observer 패턴에서 Event Provider가 Mediator 패턴에서는 Mediator에 대응되죠. 그런데 Observer 패턴은 Observer 객체들에 보내어질 메시지의 형태가 비교적 정형화되어 있다는 점에서 Mediator 패턴과는 좀 다릅니다. 위에서 요구사항을 기술하면서 보았듯이, Mediator 패턴의 Colleague 객체들은 역할이 다 달라요. 물론 관리자들이 들고 다니는 단말기 같은 객체야 Observer 패턴에 어떻게 잘 엮을 수 있겠지만, DBMS나 센서 같은 것은 좀 곤란하죠.

거기다, 옵저버 패턴처럼 객체간에 주고받아야 할 인자의 형태를 과도하게 단순화해버리면, 또다른 문제가 생길 수도 있습니다. 가령 위에 보면 Colleague 인터페이스에 receiveNotificationFromMediator라는 함수가 정의되어 있는데, DBMS나 단말이나 센서나 전부 이 함수를 통해 자신에게 오는 메시지를 수신하게 되면, 결국 인자로 전달되는 값을 파싱해야 한다는 귀찮은 상황에 직면하게 되어버리죠. 거기다 Colleague 객체가 DBMS인 경우, 단말인 경우, 센서인 경우 각각에 대해서 전송되는 데이터도 다 다른 형식일텐데, 그러면 끔찍하게 많은 양의 파싱 코드들을 작성해야 하지 않겠어요? 거기다 Concrete Mediator 클래스들을 구현할 때, 위에서처럼 NodeType의 값이 무엇이냐를 검사하는 분기문이 필요하게 될 수도 있으니, 결과적으로는 if-else를 너무 과도하게 사용하게 될지도 모른다는 문제도 있어요.

그러니, 옵저버 패턴처럼 너무 과도하게 일반화된 코딩 방법을 Mediator 패턴에 적용하는 것은 그다지 좋은 생각이 아닐지도 몰라요. 특히 여러 가지 다른 용도의 서로 다른 클래스들이 상호연동해야 하는 상황에서는, 더더욱 그럴지도 모르죠.

자. 그러면 다시 원래 물음으로 돌아가 봅시다. Mediator는 꼭 인터페이스여야 할까요? 일단 위에서 한 이야기도 있고 하니, 위의 Mediator 인터페이스 코드에서 Mediator<->Colleague 객체 간 정보 전송을 위해 필요했던 함수 receiveNotificationFromColleague는 빼버리도록 하죠. 어쨰 함수 하나만 달랑 있으니 무척이나 심심하군요. 굳이 Mediator를 인터페이스로 하지 않고 클래스도 하더라도 괜찮겠다는 생각이 점점 더 굳어집니다. :-( 실제로 이 링크에 가보시면 Mediator를 그냥 클래스로 구현한 사례를 보실 수도 있어요. 그러니 일단 이번 글에서는, Mediator를 클래스로 구현해 버리도록 하겠습니다.

남은 이야기는 2부에 이어 쓰도록 할께요. 지금은 시간이 별로 없군요 -_-;




Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/25 관련글 쓰기

댓글을 달아 주세요