디자인 패턴/GoF

[디자인 패턴] 행동 패턴 (7) 중재자 Mediator

로파이 2021. 3. 23. 00:54

중재자 (Mediator) 패턴

 

1. 의도

 

같은 집합에 속해있는 객체들의 상호작용을 중재해주는 객체를 정의하고 상호작용을 하는 객체 간 결합도를 낮추도록 한다.

 

2. 활용

 

객체간의 상호작용에는 먼저 사건이 발생하고 다른 객체가 영향을 받는 등의 종속성이 존재하게 되는데, 이 복잡한 관계를 중재하고 객체 들은 서로 참조자를 관리하지 않고 중재자 객체만 알도록 하여 결합도를 낮추도록 한다. 결합도를 낮추게 되면 관리되는 객체들의 재사용성을 증가시킬 수 있다.

 

- FontDialogDirector 클래스

 

FontDialogDirector 객체는 대화 상자를 구성하는 위젯 간의 상호작용을 중재한다. 대화 상자를 구성하는 요소중 ListBox는 마우스로 클릭할 수 있는 모든 리스트 정보를 관리한다. 사용자가 마우스로 ListBox 목록 중 하나를 클릭했을 때, 이 목록을 EntryField 객체의 출력 부분에 표시하여야 하고 이러한 상호작용을 중재자가 대신 전해주도록 한다.

버튼에 따라 폰트가 바뀌거나 사이즈가 바뀌는 것도 모든 위젯에 대한 상호작용을 포함할 수 있다.

 

- 상호작용 다이어그램

상호작용 다이어그램

리스트 상자의 선택으로 일어날 수 있는 일련의 이벤트는 다음과 같다.

  1. WidgetChanged(): 리스트 상자는 지시자(Director) 객체에 자신이 변경되었음을 알린다.
  2. GetSelection(): 지시자는 리스트 상자에서 선택된 부분이 어디인지 알아온다.
  3. SetText(): EntryField의 출력 텍스트를 설정한다.
  4. 관련 변경에 대한 자신의 상태를 수정하고 표시한다. 

- FontDialogDirector 지시자 클래스 특징

  • 자신을 구성하는 Widget 객체를 생성하는 팩토리 메서드 CreateWidgets()
  • 자신의 상태에 따라 폰트로 내용을 표시하는 ShowDialog()
  • Widget 참조자를 전달받아 어떤 위젯이 변경되었는 지 알림을 받는 WidgetChanged(Widget)

DialogDirector

위젯은 변경되었을 시 서로 메세지를 주고받지 않고 지시자를 통해 변경을 알리도록 한다.

 

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() 연산이 복잡해지면 대화 상자도 복잡해지며, 중재자 패턴의 장점이 희석될 수 도 있다.