중재자 (Mediator) 패턴
1. 의도
같은 집합에 속해있는 객체들의 상호작용을 중재해주는 객체를 정의하고 상호작용을 하는 객체 간 결합도를 낮추도록 한다.
2. 활용
객체간의 상호작용에는 먼저 사건이 발생하고 다른 객체가 영향을 받는 등의 종속성이 존재하게 되는데, 이 복잡한 관계를 중재하고 객체 들은 서로 참조자를 관리하지 않고 중재자 객체만 알도록 하여 결합도를 낮추도록 한다. 결합도를 낮추게 되면 관리되는 객체들의 재사용성을 증가시킬 수 있다.
- FontDialogDirector 클래스
FontDialogDirector 객체는 대화 상자를 구성하는 위젯 간의 상호작용을 중재한다. 대화 상자를 구성하는 요소중 ListBox는 마우스로 클릭할 수 있는 모든 리스트 정보를 관리한다. 사용자가 마우스로 ListBox 목록 중 하나를 클릭했을 때, 이 목록을 EntryField 객체의 출력 부분에 표시하여야 하고 이러한 상호작용을 중재자가 대신 전해주도록 한다.
버튼에 따라 폰트가 바뀌거나 사이즈가 바뀌는 것도 모든 위젯에 대한 상호작용을 포함할 수 있다.
- 상호작용 다이어그램
리스트 상자의 선택으로 일어날 수 있는 일련의 이벤트는 다음과 같다.
- WidgetChanged(): 리스트 상자는 지시자(Director) 객체에 자신이 변경되었음을 알린다.
- GetSelection(): 지시자는 리스트 상자에서 선택된 부분이 어디인지 알아온다.
- SetText(): EntryField의 출력 텍스트를 설정한다.
- 관련 변경에 대한 자신의 상태를 수정하고 표시한다.
- FontDialogDirector 지시자 클래스 특징
- 자신을 구성하는 Widget 객체를 생성하는 팩토리 메서드 CreateWidgets()
- 자신의 상태에 따라 폰트로 내용을 표시하는 ShowDialog()
- Widget 참조자를 전달받아 어떤 위젯이 변경되었는 지 알림을 받는 WidgetChanged(Widget)
위젯은 변경되었을 시 서로 메세지를 주고받지 않고 지시자를 통해 변경을 알리도록 한다.
3. 참여자
- Mediator(DialogDirector): Colleague 객체와 교류하는데 필요한 인터페이스를 제공한다.
- ConcreteMediator(FontDialogDirector): Colleague 객체와 조화를 이루어 협력 행동을 구현하고 Colleague 객체들을 파악하고 관리한다.
- Colleague 클래스(ListBox, EntryField): 자신의 중재자 객체가 무엇인지 파악하고 필요할 때 상호작용을 위한 메시지를 전달한다.
4. 협력 방법
- Colleague는 필요한 요청을 Mediator로 송신하거나 수신한다.
- Mediator는 Colleague 사이의 요청을 전달하도록 한다.
5. 결과
1) 서브 클래싱을 제한한다.
- 여러 객체의 행동을 하나의 객체로 국한한다.
2) Colleague 객체 사이의 종속성을 줄인다.
- 객체 간 결합도가 낮아지고 이는 객체의 재사용성과 다양성을 증가시킬 수 있다.
3) 객체 프로토콜을 단순화한다.
- 다대다 대응관계에서 일대일 관계의 상호작용에 대한 규약으로 덜 복잡하게 설정할 수 있다.
4) 객체 간의 협력 방법을 추상화한다.
- 객체 간 연결 방법에만 집중할 수 있게 된다.
5) 통제가 집중된다.
- 모든 상호작용을 처리하기 위해 중재자의 구현은 다소 복잡해질 수 있다.
6. 구현
1) 추상 클래스 Mediator 생략
- 오직 하나의 중재자 객체만 있어도 된다면 추상 클래스는 필요 없을 수도 있다.
2) 동료 객체-중재자 객체 간 의사소통
- 필요한 이벤트가 발생 시 Colleague 객체와 Mediator 객체는 정보를 주고받아야 한다. 이를 위한 구현으로 감시자 패턴을 활용하여 상태 변화 시에만 중재자에게 통보를 하여 정보를 주고받을 수 있도록 하는 방법도 있다.
예제 코드
DialogDirector 지시자 객체 구현을 알아보도록 한다.
DialogDirector.h
// 지시자 (Director)에 대한 추상 클래스 입니다.
class DialogDirector
{
public:
virtual ~DialogDirector();
// 다이얼로그를 표시합니다.
virtual void ShowDialog();
// 어떤 위젯이 변경된 사실을 알림 받습니다.
virtual void WidgetChanged(class Widget*) = 0;
};
Widget.h
위젯에 대한 기본 추상 클래스로 자신의 지시자가 누구인지 관리하도록 한다.
// 위젯에 대한 추상 클래스
class Widget
{
public:
Widget(DialogDirector*);
// 자신의 상태를 변경합니다.
virtual void Changed();
virtual void HandleMouse(class MouseEvent& event);
// ...
private:
// 자신을 감독하는 지시자를 가지고 있습니다.
DialogDirector* _director;
};
#include "Widget.h"
#include "DialogDirector.h"
Widget::Widget(DialogDirector*)
{
}
void Widget::Changed()
{
// 자신의 상태 변경을 알립니다.
_director->WidgetChanged(this);
}
void Widget::HandleMouse(MouseEvent& event)
{
// 마우스 입력에 대한 처리를 합니다.
}
Widget은 자신의 상태가 변경되었을 때, 지시자에게 변경되었음을 알리도록 한다.
ListBox.h / EntryField.h / Button.h
Widget의 구체 클래스로 FontDialogDirector를 구성하는 요소이자 상호작용을 위한 인터페이스를 제공한다.
선택된 영역에 대한 내용을 알려주거나 지시자에 의해 자신의 표시 텍스트가 설정되기도 한다.
// 구체적인 사용자 인터페이스
class ListBox : public Widget
{
public:
ListBox(DialogDirector*);
// 선택된 항목이 어떤 내용을 가지고 있는지 알려줍니다.
virtual const char* GetSelection();
// 모든 선택 가능 항목을 설정합니다.
virtual void SetList(List<char*>* listItem);
virtual void HandleMouse(MouseEvent& event);
};
class EntryField : public Widget
{
public:
EntryField(DialogDirector*);
// 해당 엔트리 내용을 가져옵니다.
virtual const char* GetText();
// 해당 엔트리 내용을 설정합니다.
virtual void SetText(const char* text);
virtual void HandleMouse(MouseEvent& event);
};
class Button : public Widget
{
public:
Button(DialogDirector*);
virtual void SetText(const char* text);
virtual void HandleMouse(MouseEvent& event)
{
// 마우스 움직임에 따라 상호작용 구현
Changed();
}
// ...
};
FontDialogDirector.h
대화 상자에 정의한 위젯 간의 중재 역할을 수행한다.
#pragma once
#include "DialogDirector.h"
class Button;
class ListBox;
class EntryField;
class FontDialogDirector : public DialogDirector
{
public:
FontDialogDirector();
virtual ~FontDialogDirector();
virtual void WidgetChanged(Widget*);
protected:
// 팩토리 메서드
virtual void CreateWidgets();
private:
Button* _ok;
Button* _cancel;
ListBox* _fontList;
EntryField* _fontName;
};
#include "FontDialogDirector.h"
#include "Button.h"
#include "ListBox.h"
#include "EntryField.h"
FontDialogDirector::FontDialogDirector()
{
}
FontDialogDirector::~FontDialogDirector()
{
}
void FontDialogDirector::CreateWidgets()
{
_ok = new Button(this);
_cancel = new Button(this);
_fontList = new ListBox(this);
_fontName = new EntryField(this);
// 가능한 폰트 이름들로 리스트 상자가 채워집니다.
// 생성한 위젯들을 대화상자 안에 조합합니다.
}
void FontDialogDirector::WidgetChanged(Widget* theChangedWidget)
{
if (theChangedWidget == _fontList)
{
_fontName->SetText(_fontList->GetSelection());
}
else if (theChangedWidget == _ok)
{
// 폰트 변경을 적용하고 대화상자를 없앱니다.
}
else if (theChangedWidget == _cancel)
{
// 대화상자를 없앱니다.
}
}
자신을 구성하는 위젯들을 생성하며 자신을 지시자로 갖도록 설정한다.
WidgetChanged() 연산이 복잡해지면 대화 상자도 복잡해지며, 중재자 패턴의 장점이 희석될 수 도 있다.
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 (9) 메멘토 Memento (0) | 2021.03.29 |
---|---|
[디자인 패턴] 행동 패턴 (8) 반복자 Iterator (0) | 2021.03.24 |
[디자인 패턴] 행동 패턴 (6) 전략 Strategy (0) | 2021.03.13 |
[디자인 패턴] 행동 패턴 (5) 감시자 Observer (0) | 2021.02.04 |
[디자인 패턴] 행동 패턴 (4) 상태 State (0) | 2021.02.03 |