1. Mediator


1.1. Intent


MediatorPattern은 객체들의 어느 집합들이 interaction하는 방법을 encapsulate하는 객체를 정의한다. Mediator는 객체들을 서로에게 명시적으로 조회하는 것을 막음으로서 loose coupling을 촉진하며, 그래서 Mediator는 여러분에게 객체들의 interactions들이 독립적으로 다양하게 해준다.

1.2. Motivation


Object-Oriented 디자인은 객체들 사이의 행위 분산을 장려한다. 그런 분산은 객체들 사이에 많은 연관을 지닌 객체 구조로 나타날 수 있다. 최악의 경우에는 모든 객체가 결국 모든 다른 객체들을 알게 된다.

비록 하나의 시스템에 많은 객체들이 참여하는 것이 일반적으로 재사용성을 강화할지라도 interconnections이 늘어나는 것은 재사용성을 감소시키려는 경향이 있다. 너무나 많은 객체간의 상호 연결들은 객체들의 독립성을 떨어뜨릴 수 있다. - 그런 시스템은 마치 완전히 통일된 것 같이 행동한다.

게다가 하나의 시스템에 많은 객체들이 참여하는 것은 어떤 의미있는 방법으로 시스템의 행위를 바꾸는 것을 어렵게 한다. 왜냐하면, 행위는 많은 객체들 사이로 분산되어 졌기 때문이다. 결론적으로 당신은 아마 그런 시스템의 행위를 customize하기 위해서 수많은 subclass들을 정의해야 할 것이다.

예로써 어떤 GUI상에서 다이얼로그 박스의 구현을 고려해보자. 하나의 다이얼로그 박스는 작은 도구들(버튼, 메뉴, 입력 필드)의 모음을 표현하는 하나의 윈도우를 사용한다.


대게 다이얼로그의 도구들 사이에는 어떤 dependency들이 존재한다. 예를 들면, 어떤 버튼은 어떤 입력 필드가 비어있을때는 비활성화 되어있는다. list box라 불리는 선택 목록에서 객체를 선택하는 것은 입력필드의 내용을 바꿀 것이다. 바꿔말하면, 입력필드에 문자를 타이핑하는 것은 자동적으로 리스트 박스에서 하나이상의 대응대는 입력을 선택하는 것이다. 한번 텍스트가 입력 필드에 나타나면, 다른 버튼들은 아마 활성화 될것이다. 그래서 사용자가 텍스트로 어떤 일을 하게 하게할 것이다. 예를 들자면, 관련있는 것을 삭제하거나 변경하거나 하는 따위의 일을 할 수 있을 것이다.

다른 다이얼로그 박스들은 도구들 사이에서 다른 dependency들을 지닐 것이다. 그래서 심지어 다이얼로그들이 똑같은 종류의 도구들을 지닌다 하더라도, 단순히 이전의 도구 클래스들을 재사용 할 수는 없다. dialog-specific dependency들을 반영하기 위해서 customize되어져야 한다. subclassing에 의해서 개별적으로 도구들을 Customize하는 것은 지루할 것이다. 왜냐하면 많은 클래스들이 그렇게 되어야 하기 때문이다.

별개의 mediator 객체에서 집단의 행위로 encapsulate하는 것에 의해서 이런 문제를 피할 수 있다. 하나의 mediator는 객체들 그룹 내의 상호작용들을 제어하고 조정할 책임이 있다. 그 mediator는 그룹내의 객체들이 다른 객체들과 명시적으로 조회하는 것을 막는 중간자로서의 역할을 한다. 그런 객체들은 단지 mediator만 알고 있고, 고로 interconnection의 수는 줄어 들게 된다.

예를 들면, FontDialogDirector는 다이얼로그 박스의 도구들 사이의 mediator일 수 있다. FontDialogDirector객체는 다이얼로그 도구들을 알고 그들의 interaction을 조정한다. 그것은 도구들 사이의 communication에서 hub와 같은 역할을 한다.


다음 interaction diagram은 객체들이 리스트박스의 선택에서 변화를 다루기 위해 협동하는 방법을 묘사하고 있다.


Here's the succession of events by which a list box's selection passes to an entry field.
여기서는 list box에서의 선택이 entry field 로 전달되고 있는 이벤트들의 흐름이 있다.
  1. 리스트 박스가 그것의 director에게 그것이 변했다고 말한다.
  2. director는 리스트 박스로 부터 선택을 얻는다.
  3. director는 입력 필드로 선택을 넘긴다.
  4. 이제 입력 필드는 어떤 문자를 포함한다. director는 행동(글씨를 굵게 하거나 기울이게 하는 따위의 행동)의 초기화를 위해 버튼을 활성화 한다.

director가 리스트 박스와 입력 필드 사이의 조정하는 방법을 요약하자. 도구들은 서로 단지 간접적으로 director을 통해서 통신한다. 그들은 서로에 대해서 몰라야 하며, 그들 모두는 director를 알아야 한다. 게다가 행위는 한 클래스에 지역화 되어지기 때문에 행위는 클래스를 확장하거나 교체함으로써 변하거나 바꿔질 수 있다.

FontDialogDirector 추상화가 클래스 library를 통하하는 방법은 다음과 같다.


DialogDirect는 다이얼로그의 전체 행위를 정의한 추상 클래스이다. client들은 화면에 다이얼로그를 나타내기 위해서 ShowDialog 연산자를 호출한다. CreateWidgets는 다이얼로그 도구들을 만들기 위한 추상 연산자이다. WidgetChanged는 또 다른 추상 연산자이며, 도구들은 director에게 그들이 변했다는 것을 알려주기 위해서 이를 호출한다. DialogDirector subclass들은 CreateWidgets을 적절한 도구들을 만들기 위해서 override하고 그리고 그들은 WidgetChanged를 변화를 다루기 위해서 override한다.

1.3. Applicability

MediatorPattern은 이럴 때 사용한다.
  • 어떤 객체들의 집합이 잘 정의되었지만, 복잡한 방법으로 통신할 때. interconnection의 결과는 구조화되지 못하고 이해를 어렵게 한다.
  • 어떤 객체를 재사용하는 것이 그것이 많은 다른 객체들과 관련이 있고 통신을 하기 때문에 어려울 때.
  • 몇몇의 클래스들 사이에 분산되어진 하나의 행위가 많은 subclassing하는 작업 없이 customize되어져야 할 때.

1.4. Structure



전형적인 객체 구조는 이렇게 보일 것이다:


1.5. Participants

  • Mediator(DialogDirector)
    Colleague 객체들과 통신을 위해서 인터페이스를 정의한다.
  • ConcreteMediator(FontDialogDirector)
    Colleague 객체들을 조정함으로써 엽합 행위를 구현한다.
    자신의 colleague들을 알고 관리한다.
  • Colleague classes(listBox, Entry Field)
    각각의 colleague class는 자신의 Mediator 객체를 안다.
    각가의 colleague 는 자신이 다른 colleague와 통신할 때마다 자신의 mediator와 통신한다.

1.6. Collaborations

Colleague들은 Mediator 객체에게 요청을 보내고 받는다. Mediator는 적절한 colleague에게 요청을 보냄으로써 협동 행위를 구현한다.

1.7. Consequences

Mediator Pattern은 다음과 같은 장점과 단점을 지닌다.
  1. MediatorPattern은 subclassing을 제한한다. mediator는 다시말해 몇몇개의 객체들 사이에 분산되어질 행위를 집중한다. 이런 행위를 바꾸는 것은 단지 Mediator를 subclassing하기만 하면 된다. Colleague 클래스들은 재사용되어질 수 있다.
  2. MediatorPattern은 colleague들을 떼어놓는다. Mediator는 colleague들 사이에서 loose coupling을 촉진한다. colleagued와 Mediator를 개별적으로 다양하게 할 수 있고, 재사용 할 수 있다.
  3. MediatorPattern은 객체 protocols을 단순화 시킨다. Mediator는 다대다 상호관계를 Mediator와 colleague들 사이의 일대다 관계로 바꾸어 놓는다. 일대다 관계는 이해, 관리, 확장하는데 더 쉽다.
  4. MediatorPattern은 객체가 협동하는 방법을 추상화 시킨다. Mediation를 독립적인 개념으로 만들고 하나의 객체에 캡슐화하는 것은 여러분으로 하여금 객체의 행위는 제쳐두고 그 interaction에 집중하게 해준다. 이는 객체가 시스템 내에서 어떻게 interact하는 방법을 명확히 하는데 도움을 준다.
  5. MediatorPattern은 제어를 집중화한다. Mediator는 interaction의 복잡도를 mediator의 복잡도와 맞바꿨다. Mediator가 protocol들을 encapsulate했기 때문에 colleague객체들 보다 더 복잡하게 되어질 수 있다. 이것이 mediator를 관리가 어려운 monolith 형태를 뛰게 만들 수 있다.

1.8. Implementation

다음 구현과 관련된 issue들은 MediatorPattern과 관련이 있다.
  1. 추상 Mediator 클래스 생략하기. 추상 Mediator 클래스를 선언할 필요가 없는 경우는 colleague들이 단지 하나의 mediator와만 작업을 할 때이다. Mediator클래스가 제공하는 추상적인 coupling은 colleague들이 다른 mediator subclass들과 작동학게 해주며 반대의 경우도 그렇다.
  2. Colleague-Mediator communication. colleague들은 그들의 mediator와 흥미로운 이벤트가 발생했을 때, 통신을 해야한다. 한가지 방법은 mediator를 Observer로서(ObserverPattern을 이용해서) 구현하는 것이다. colleague 객체들은 Subject들로서 작동하고, 자신의 상태가 변했을 때, 지시를 Mediator에게 전달한다. Mediator는 변화의 효과를 다른 colleague들에게 전달하는 반응을 한다.

또 다른 방법은 colleague들이 보다 더 직접으로 communication할 수 있도록 특별한 interface를 mediator에게 심는 것이다. 윈도우용 Smalltalk/V가 대표적인 형태이다. mediator와 통신을 하고자 할 때, 자신을 argument로 넘겨서 mediator가 sender가 누구인지 식별하게 한다. Sample Code는 이와 같은 방법을 사용하고 있고, Smalltalk/V의 구현은 Known Uses에서 다루기로 하겠다.

1.9. Sample Code

우리는 DialogDirector를 Motivation에서 보았던 것처럼 font dialog를 구현하기 위해서 사용할 것이다. 추상 클래스 DialogDirector는 director들을 위한 interface를 정의 하고 있다.
~cpp 
	class DialogDirector {
	public:
		virtual ~DialogDirector();

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

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

Widget 은 widgets들을 위한 추상 기초 클래스이다. 하나의 widget은 자신의 director를 알고 있다.
~cpp 
	class Widget {
	public:
		Widget(DialogDirector*);
		virtual void Changed();

		virtual void handleMouse(MouseEvent& event);
		//...
	private:
		DialogDirector* _director;
	};

changed 는 director의 WidgetChanged 연산을 호출한다. Widget들은 자신의 director의 WidgetChanged 호출을 의미있는 이벤트를 알져주기 위해서 사용한다.
~cpp 
	void Widget::Changed() {
		_director->WidgetChanged(this);
	}

DialogDirector의 subclass들은 적절한 widget작동하기 위해서 WidgetChanged를 override해서 이용한다. widget은 자신의 referece를 WidgetChanged에 argument로서 넘겨줌으로서 어떤 widget의 상태가 바뀌었는지를 director로 하여금 알게해준다. DialogDirector의 subclass들은 CreateWidget 순수 추상 연산자를 다이얼로그에 widget들을 만들기 위해 재정의한다.

ListBox, EntryField, Button은 특화된 사용자 인터페이스 요소를 위한 DialogDirector의 subclass들이다. ListBox는 현재 선택을 위해서 GetSelection연산자를 제공한다. 그리고 EntryFieldSetText 연산자는 새로운 text로 field를 채운다.

~cpp 
	class ListBox:public Widget {
	public:
		ListBox(DialogDirector*);

		virtual const char* GetSelection();
		virtual void SetList(List* listItems);
		virtual void HandMouse(MouseEvent& event);
		//..
	};

	class EntryField:public Widget {
	pblic:
		EntryField(DialogDirector*);
		virtual void SetText(const char* text);
		virtual const char* GetText();
		virtual void handleMouse(MouseEvent& event);
		//...
	};

Button은 그것이 눌러졌을 때, Changed를 호출하는 단순한 widget이다. 이는 HandleMouse를 구현함으로써 되어진다.
~cpp 
	class Button : public widget {
	public:
		button(DialogDirector*);

		virtual void SetText(const char* text);
		virtual void HandleMouse(MouseEvent& event);
		//...
	};
	
	void Button::HandleMouse(MouseEvent& event) {
		//....
		Change();
	}

FontDialogDirector 클래스는 다이얼로그 박스의 widgets사이에 중간이 위치한다. FontDialogDirectorDialogDirector의 하위 클래스이다.
~cpp 
	class FontDialogDirector:public DialogDirector {
	public:
		FontDialogDirector();
		virtual ~FontDialogDirector();
		virtual void WidgetChanged(Widget*);

	protected:
		virtual void CreateWidgets();

	private
		Button* _ok;
		Buton* _cancel;
		ListBox* _fontList;
		entryField* _fontName;
	};

FontDialogDirector는 그것이 display하는 widget을 추적한다. 그것은 widget들을 만들기 위해서 CreateWidget을 재정의하고 그것의 reference로 그것들을 초기화한다.
~cpp 
	void FontDialogDirector::CreateWidgets() {
		_ok = new Button(this);
		_cnacel = new Button(this);
		_fontList = new ListBox(this);
		_fontName = new EntryField(this);

		// fill the listBox white the available font names

		// assemble the widgets in the dialog
	};

WidgetChanged는 widget들이 서로 적절하게 동작하는 것을 확신하게 한다.
~cpp 
	void FontDialogDirector::WidgetChanged(
		Widget* theChangedWidget
	) {
		if(theChangedWidget ==_fontList) {
		_fontName->GetTet(_fontList->GetSelection());
	  } else if (theChangedWidget == _ok) {
		//apply font chnage and dismiss dialog
		//...
	  } else if (theChangedWidget ==_cancel) {
		//dismiss dialog
	  }
	}

WidgetChanged의 복잡성은 그만큼 다이얼로그 복잡성을 높인다. 물론 큰 다이얼로그는 다른 이유로 인해서 바람직하지 못하지만, mediator 복잡성은 다른 application들에서 패턴의 잇점을 완화시킨다.

1.10. Known Uses

ET++WGM88와 THINK C class librarySm93b는 다이얼로그에서 widget들 사이에 mediator로서 director와 유사한 객체를 사용한다.

윈도우용 Smalltalk/V의 application구조는 mediator 구조에 가반을 두고 있다.LaL94 그런 환경에서 application은 윈도우를 pane들의 모음으로 구성하고 있다. library는 몇몇의 이미 정의된 pane들을 가지고 있다. 예를 들자면 TextPane, ListBox, Button등등이 포함된다. 이러한 pane들은 subclassing없이 이용될 수 있다. Application 개발자는 단지 inter-pane coordination할 책임이 있는 ViewManager만 subclassing할 수 있다. ViewManage는 Mediator이고 각각의 pane들은 자신의 owner로서 단지 자신의 ViewManager를 알고 있다. pane들은 직접적으로 서로 조회하지 않는다.

다음 object diagram은 run-time에 application의 snapshot을 보여주고 있다.


SmallTalk/V는 Pane-ViewManager 통신을 위해 event 기법을 사용하고 있다. 어떤 pane은 어떤 정보를 mediator로 부터 얻기 원하거나 어떤 의미있는 일이 발생해서 이를 mediator에게 알려주기 위해서 event를 생성한다. 하나의 event는 그 event를 식별하는 symbol을 정의한다. 그 event를 다루기 위해서 ViewManager는 pane에 method selector를 등록한다. 이 selector는 event의 handler이다. 이것은 event가 발생한 때면 언제든지 수행될 것이다.

다음 코드 인용은 ListBoxViewManager subclass 내에서 만들어지는 방법과 #select event를 위해 ViewManager가 event handler를 등록하는 방법을 보여주고 있다.
~cpp 
     self addSubpane: (ListPane new
          paneName: 'myListPane';
          owner: self;
          when: #select perform: #listSlet:).

MediatorPattern의 또다른 application은 coordinating complex updates에 있다. 하나의 예는 Observer로서 언급되어지는 ChangeManager class이다. ChangeManager는 중복 update를 피하기 위해서 subjects과 observers중간에 위치한다. 객체가 변할때, ChangeManager에게 알린다. 그래서 ChangeManager는 객체의 dependecy를 알리는 것으로 update를 조정한다.

유사한 application은 Unidraw drawing framework에서 나타나고VL90 connectors사이에 연결성 제약들을 적용하는 CSolver라 불리는 class를 사용한다. 그래픽 편집기에서 객체들은 다른 방법으로 서로 다른 객체들을 짜집는 것으로 보일 수 있다. connector들은 연결성이 자동적으로 관리되는 그림 편집기나 회로 설계 시스템과 같은 application들에서 유용하다. CSolver는 객체들 사이에 mediator이다. 그것은 연결제약을 해결하고, connector들의 위치를 그것들을 반영하기 위해서 update한다.

1.11. Related Patterns

FacadePattern(185)은 보다 편리한 인터페이스를 제공하고자 subsystem의 객체들을 추상화시킨 Mediator와 다르다. 그것의 protocol은 간접적이다. 다시 말하면 Facade 객체들은 subsystem의 요청들을 만들지만 반대의 경우는 그렇지 못하다. 대조적으로 Mediator는 colleague들이 제공하지 못하거나 할 수 없는 협동적인 행위를 가능하게 해준다. 그래서 그 protocol은 multidirectional하다.

기본적으로 FacadePattern은 클래스 집단이 있고, 그 클래스 집단을 사용하는 외부 클래스의 입장에서 필요한 패턴이고, MediatorPattern은 클래스 집단이 있고, 그 클래스 집단 내부에서 서로를 사용하기 위한 패턴이다.

예컨대, Seminar:ElevatorSimulation에 여러가지 배우(엘레베이터, 사람, 층, ...)들이 존재할 경우, 사람은 층을 참조, 사용하고, 층은 다시 갖고 있는 사람을 참조하고, 엘레베이터는 사람을 참조, 사용하고 하는 식으로 복잡한 (순환) 의존관계가 존재한다. 이럴 때 MediatorPattern을 쓰게되면 이 복잡한 의존고리를 끊을 수 있다.

colleague들은 observer(293) pattern을 이용하는 Mediator와 통신할 수 있다.


Facade 와 Mediator 를 가끔 혼동했었는데. 희록이형 감사감사요~~ --1002



Retrieved from http://wiki.zeropage.org/wiki.php/Gof/Mediator
last modified 2021-02-07 05:23:18