Mediator Pattern
객체들이 서로를 직접 참조하지 않고 중재자를 통해서만 소통하게 만드는 패턴. 다대다로 얽힌 의존을 중재자 하나로 모아 결합을 푼다.
난이도 중급 · 선행 Observer
한 줄 요약
여러 객체가 서로를 직접 부르며 얽히면 의존이 다대다로 폭발한다. 이 상호작용을 중재자(Mediator) 한 곳에 모아, 각 객체는 중재자만 알고 서로는 모르게 한다.
어떤 문제를 푸는가
회원가입 폼을 떠올려보자. 위젯들이 서로의 상태에 반응한다.
- “약관 동의” 체크 → “가입” 버튼 활성화
- “아이디” 입력 → “중복확인” 버튼 활성화
- “중복확인” 통과 → “아이디” 칸 잠금
각 위젯이 서로를 직접 들고 있으면:
1
2
3
4
5
6
7
8
9
10
11
12
13
class AgreeCheckbox {
SignupButton* signupBtn; // 다른 위젯을 직접 참조
public:
void onToggle(bool checked) {
signupBtn->setEnabled(checked); // 직접 호출
}
};
class IdField {
DupCheckButton* dupBtn; // 또 다른 위젯을 직접 참조
SignupButton* signupBtn; // 그리고 또...
void onType() { /* 여기저기 직접 호출 */ }
};
이 코드의 통증:
- 위젯이 N개면 의존선이 최대 N×N으로 늘어난다. 새 위젯 하나가 모두를 건드린다.
AgreeCheckbox를 다른 폼에서 재사용할 수 없다.SignupButton을 콕 집어 알기 때문.- “이 폼의 규칙”이 위젯마다 흩어져 전체 흐름이 안 보인다.
패턴 적용 후
위젯끼리의 직접 참조를 끊고, 모두 중재자(폼) 에게만 알린다. 규칙은 중재자 한 곳에 모인다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Mediator {
public:
virtual void notify(Widget* sender, const std::string& event) = 0;
virtual ~Mediator() = default;
};
class Widget {
protected:
Mediator* mediator;
public:
explicit Widget(Mediator* m) : mediator(m) {}
};
class AgreeCheckbox : public Widget {
public:
using Widget::Widget;
void toggle(bool checked) {
mediator->notify(this, checked ? "agreed" : "disagreed"); // 누가 받는지 모른다
}
};
// 규칙이 여기 한 곳에 모인다
class SignupForm : public Mediator {
AgreeCheckbox agree{this};
SignupButton signupBtn;
IdField idField{this};
public:
void notify(Widget* sender, const std::string& event) override {
if (sender == &agree)
signupBtn.setEnabled(event == "agreed");
else if (sender == &idField && event == "verified")
idField.lock();
}
};
달라진 점:
- 위젯은 중재자만 안다.
AgreeCheckbox는 다른 폼에 그대로 재사용된다. - 폼의 상호작용 규칙이
notify한 곳에 모여, 전체 흐름이 위에서 아래로 읽힌다.
구조
- Mediator: 중재 인터페이스 (
notify) - ConcreteMediator: 동료들을 보유하고 규칙을 구현 (
SignupForm) - Colleague: 중재자만 참조하는 객체들 (
AgreeCheckbox,IdField)
1
2
3
4
Widget A Widget B Widget C
│ │ │
└────────▶ Mediator ◀──────────────┘
(모든 상호작용이 여기로)
의존이 N×N(서로)에서 N×1(중재자)로 줄어든다.
실전 사례
- 항공 관제탑: 비행기들이 서로 직접 교신하지 않고 관제탑을 통한다 — 이 패턴의 교과서적 비유.
- 채팅방: 참가자들은 서로를 모르고 방(서버)에 메시지를 보낸다. 방이 중재자.
- GUI 다이얼로그: 위 예제처럼 컨트롤 간 활성화·검증 규칙을 폼/컨트롤러가 중재.
- MVC의 Controller: 뷰와 모델을 직접 엮지 않고 컨트롤러가 사이를 중재하는 형태로 볼 수 있다.
Observer Pattern과의 차이
둘 다 “직접 호출을 끊어 결합을 푼다”는 점이 같아 헷갈린다.
| Observer | Mediator | |
|---|---|---|
| 방향 | 일대다 브로드캐스트 (Subject → 구독자들) | 다대다 조율 (중재자가 양방향 규칙 처리) |
| 로직 위치 | 통지만, 규칙은 각자 | 상호작용 규칙이 중재자에 집중 |
| 관계 | 발행자는 구독자가 누군지 모름 | 중재자는 동료들을 다 알고 조율 |
Observer는 “나 바뀌었어”를 뿌리는 것, Mediator는 “이때 이렇게 해”를 한 곳에서 지휘하는 것. 자세한 통지 메커니즘은 Observer 패턴 글 참고.
안티패턴 / 주의
- 중재자가 갓 오브젝트(God Object)가 되기 쉽다. 모든 규칙을 빨아들여 거대해지면, 결합을 푼 게 아니라 한 클래스로 옮겨담은 것뿐이다.
- 상호작용이 단순하거나 위젯이 둘뿐이면 도입하지 말 것. 직접 참조가 더 읽기 쉽다.
- 중재자가 커지면 영역별로 쪼개거나, 단순 통지로 충분한 부분은 Observer로 분리한다.
스스로 점검
1. 위젯끼리 직접 참조하면 의존선이 왜 폭발하나?
답
각 위젯이 반응해야 할 다른 위젯을 모두 알아야 하므로, N개 위젯이면 최대 N×N 의존이 생긴다. 중재자를 두면 각 위젯은 중재자 하나만 알아 N×1로 준다.
2. Observer와 Mediator를 가르는 가장 명확한 기준은?
답
방향과 로직 위치. Observer는 일대다 브로드캐스트(발행자는 구독자를 모름), Mediator는 다대다 조율(중재자가 동료를 다 알고 규칙을 집중 관리). “뿌리기”면 Observer, “지휘”면 Mediator.
3. Mediator를 잘못 쓰면 어떤 냄새가 나나?
답
중재자가 모든 규칙을 빨아들여 거대한 갓 오브젝트가 된다. 결합을 푼 게 아니라 한 곳으로 옮겨담은 것이라, 영역별로 분리하거나 일부를 Observer로 빼야 한다.