의도
객체의 상호작용을 캡슐화하는 객체.

객체들이 직접 서로를 참조하지 않도록 하여 객체 사이의 loose coupling 을 촉진.
개발자가 객체의 상호작용을 독립적으로 다양화 시킬 수 있다.

동기
객체지향 개발 방법론에 의해 우리는 행동을 여러 객체에게 분산시켜 처리한다. 
-> 수많은 연결관계가 객체 사이에 존재하게 됨.
-> 어떤 경우에는, 행동을 처리하기 위해 모든 객체에 대한 참조자를 관리해야 할 수도 있다.

여러 객체로 분할하게 되면 재사용성이 증가하지만, 객체 간 상호작용의 급증을 유발한다.
어떤 일 하나를 처리하기 위해서 수많은 상호작용이 필요하게 되면서, 객체는 독립적이어야 된다는 특성이 유명무실해질 수 도 있다.

분할하긴 했지만 시스템은 하나의 거대한 덩어리처럼 동작해야 한다.
행동이 여러 객체에 걸쳐 분산되어 있기때문에 시스템의 행동을 수정하기 위해서는 여러 클래스를 재정의해야 할 수 도 있다.


예를들어 Dialog를 만든다고 하자.
어떤 입력값이나 선택된 값에 의해 버튼이 비활성화된다던지, 리스트 내의 항목이 변경된다던지..
하는 종속성이 존재.

이러한 종속성은 대화상자마다 다르다. 동일한 종류의 컨트롤들로 만들어져 있더라도, 단순하게 재사용 할 수 없음.
지금 만들려는 대화상자에 맞춰 새롭게 컨트롤들을 만들어 줘야 함.
이때, 각각의 컨트롤들을 일일이 서브클래싱할 것인가?

중재자 객체를 활용하면 이런 문제를 피해 갈 수 있다.

중재자 객체는 객체 그룹 간의 상호작용을 제어하고 조화를 이루는 역할을 한다.
그룹 내 객체들에 대한 포인터를 중재자 객체가 관리.
객체들은 다른 객체에 대한 참조자 대신 중재자만 알면 된다.
즉, 객체 사이의 연결 정도가 줄어든다.

예를 들어, FontDialogDirector 객체는 대화 상자에 있는 컨트롤들을 알고, 이들 간의 상호작용을 조정한다.
즉, 컨트롤 사이에 오가는 모든 의사소통의 hub 역할을 함.

다이얼로그의 리스트상자에서 아이템을 선택하면 입력창에 해당 항목이 자동으로 채워지고, 입력창에 텍스트가 있으면 다른 버튼들이 활성화됨.
리스트 상자의 선택 변경을 처리하는 과정에서 객체 간에 어떻게 처리되었는지를 보자.

(Widget은 TOS에서 Control)

    1. 리스트 상자가 Director에게 자신이 변경되었음을 알림.
    2.  Director는 리스트상자에서 선택된게 뭔지 알아옴.
    3.  Director는 입력창에 선택 부분을 전달.
    4. 입력창에 어떤 값이 포함됨. Director는 관련된 버튼을 활성화.

컨트롤끼리는 직접 메세지를 주고 받지 않음.
서로에 대한 정보는 전혀 모른다.
단지 Director만 알 뿐.
행동들은 각각 하나의 클래스에만 정의되었기 때문에 행동변경이 아무런 문제가 되지 않음.

어떻게 FontDialogDirector 클래스를 추상화해서 클래스 라이브러리에 정의하는지 알아보자. (?)



DialogDirector는 다이얼로그의 일반적인 행동을 모두 정의하는 추상클래스.
사용자는 ShowDialog()를 콜.
CreateWidgets()은 다이얼로그에 필요한 위젯(컨트롤)들을 만드는 추상 연산.
WidgetChanged은 위젯(컨트롤)이 자신의 상태가 변경되었을 경우, Director에게 알려주는 연산.

예제코드
//추상클래스. Director 인터페이스.
class DialogDirector {
public:
virtual ~DialogDirector();

virtual void ShowDialog();
virtual void WidgetChanged() = 0;

protected:
DialogDirector();
virtual void CreateWidgets() = 0;
};

//위젯에 대한 추상 기본 클래스.
class Widget {
public :
Widget(DialogDirector*);

virtual void Changed() {
_director->WidgetChanged(this);
}
virtual void HandleMouse(MouseEvent& me);

private:
DialogDirector* _director;
};

//SelectionChanged가 없다..
class ListBox : public Widget {
public:
ListBox(DialogDirector*);

virtual const char* GetSelection();
virtual void SetList(List<char*> listItems);
virtual void HandleMouse(MouseEvent& me);
};

class EntryField : public Widget {
public :
EntryField(DialogDirector*);

virtual void SetText(const char* text);
virtual const char* GetText();
virtual void HandleMouse(MouseEvent& me);
};

class Button : public Widget {
public :
Button(DialogDirector*);

virtual void SetText(const char* text);
virtual void HandleMouse(MouseEvent& me) {
Changed();
}
//...
};

class FontDialogDirector : public DialogDirector {
public:
FontDialogDirector();
virtual FontDialogDirector();

virtual void WidgetChaged(Widget* widget) {
if (widget == _fontList) {
_fontName->SetText(_fontList->GetSelection());
}
else if (widget == _ok) {
//폰트 변경 적용하고 다이얼로그 닫음.
}
else if (widget == _cancel) {
//다이얼로그 닫기.
}
}

protected:
virtual void CreateWidgets() {
_ok = new Button(this);
_cancel = new Button(this);
_fontList = new ListBox(this);
_fontName = new EntryField(this);

//ListBox 내용 채우기..
//위치 크기 세팅 등등..
}

private:
Button* _ok;
Button* _cancel;
ListBox* _fontList;
EntryField* _fontName;
};


활용성
  • 여러객체가 잘 정의된 형태이기는 하지만 복잡한 상호작용을 가질 때. 객체간 의존성이 구조화되지 않으며 잘 이해하기 어려울 때.
  • 한 객체가 다른 객체를 너무많이 참조하고, 너무 많은 의사소통을 수행해서 그 객체를 재사용하기 힘들 때
  • 여러 클래스에 분산된 행동들이 상속 없이 상황에 맞게 수정되어야 할 때

구조


객체구조는 다음과 같다.


참여자
  • Mediator(DialogDirector) : Colleague 객체와 교류하는데 필요한 인터페이스를 정의
  • ConcreteMediator(FontDialogDirector) : Colleague 객체와 조화를 이뤄서 협력 행동을 구현하며, 자신이 맡을 Colleague를 파악하고 관리.
  • Colleague 클래스들 (ListBox, EntryField ..) : 자신의 중재자 객체가 무엇인지 파악. 다른 객체와 통신이 필요하면 중재자를 통해 통신.

협력방법
Colleague는 Mediator에서 요청을 송수신. Mediator는 필요한 Colleague 사이에 요청을 전달할 의무가 있다.

결과 (장단점)
  1. 서브클래싱을 제한한다.
    • 중재자는 다른 객체 사이에 분산된 객체의 행동을 하나의 객체로 국한한다. 이 행동을 변경하고자 한다면 Mediator 클래스를 상속하는 서브클래스만 만들면 됨. Colleague 클래스는 여전히 재사용 가능.
  2. Colleague 사이의 종속성을 줄임.
    • 중재자는 행동에 참여하는 객체 간의 소결합을 증진시킨다. 이로써 Mediator 클래스와 Colleague 클래스 각각을 독립적으로 다양화 시킬수 있고 재사용 가능.
  3. 객체 프로토콜을 단순화한다.
    • 중재자는 n:m의 관계를 1:n의 관계로 축소. 1:n이 훨씬 이해하기 쉽고, 유지/ 확장하기 쉽다.
  4. 객체 간의 협력 방법을 추상화.
    • 객체 사이의 중재를 독립적인 개념으로 만들고 이것을 캡슐화 함으로써, 사용자는 각 객체의 행동과 상관없이 객체 간 연결 방법에만 집중 할 수 있다. 결과적으로 시스템에서 객체가 어떻게 동작하는지를 좀 더 명확히 하는데 도움이 됨. (colleague가 mediator만 알고 다른 colleaue는 어떻게 동작하는지 신경쓰지 않아서?)
  5. 통제가 집중화 됨.
    • 중재자 패턴은 상호작용의 복잡한 것들이 자신 내부에서만 오가게 한다. 중재자 객체는 동료 객체 간의 상호작용에 관련된 프로토콜을 모두 캡슐화하기 때문에 어느 동료 객체보다도 훨씬 복잡해질 수 있다. 이 때문에 Mediator 클래스 자체의 유지보수가 어려워 질 수 있다.

구현
구현시 고려해야 할 이슈
  1. Mediator 생략.
    • 만약 관련 객체들이 오직 하나의 Mediator 클래스와 동작한다면 Mediator를 추상화 할 필요 X. 추상클래스의 목적은 또 다른 상호작용을 정의할 새로운 Mediator 서브클래스를 만들때 필요.
  2. Colleague-Mediator 간 의사소통.
    • 필요한 이벤트가 발생할 때 Colleague 클래스는 Mediator클래스와 자료를 주고 받아야 함. 중재자 클래스를 구현하는 한가지 방법은 나중에 나오는 감시자 패턴을 사용하는 방법이다. Colleague 객체의 상태 변화가 일어날때마다 중재자에게 통보하면, 중재자는 처리방법에 따라 다른객체들에게 변경을 통보하여 처리한다.
    • 다른 방법은 notification 인터페이스를 정의해 동료 객체들이 직접 통신하도록 하는 것. Windows 플랫폼의 스몰토크/V가 썻다고함. 잘 알려진 예에 나온다고 해서 읽어봤는데 뭔소린지 모르겠다. event 에 해당 객체를 붙여서 사용한다는데.. (메서드 선택자?) 
      • 해당 event handler로 colleague 객체를 할당한다는 뜻인가


GoF의 디자인패턴 개정판. (프로텍미디어)


'스터디 > 디자인패턴' 카테고리의 다른 글

디자인패턴 - 장식자 (Decorator)  (0) 2016.12.08
Posted by outshine90
,