포스트

Mediator Pattern

객체들이 서로를 직접 참조하지 않고 중재자를 통해서만 소통하게 만드는 패턴. 다대다로 얽힌 의존을 중재자 하나로 모아 결합을 푼다.

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과의 차이

둘 다 “직접 호출을 끊어 결합을 푼다”는 점이 같아 헷갈린다.

 ObserverMediator
방향일대다 브로드캐스트 (Subject → 구독자들)다대다 조율 (중재자가 양방향 규칙 처리)
로직 위치통지만, 규칙은 각자상호작용 규칙이 중재자에 집중
관계발행자는 구독자가 누군지 모름중재자는 동료들을 다 알고 조율

Observer는 “나 바뀌었어”를 뿌리는 것, Mediator는 “이때 이렇게 해”를 한 곳에서 지휘하는 것. 자세한 통지 메커니즘은 Observer 패턴 글 참고.

안티패턴 / 주의

  • 중재자가 갓 오브젝트(God Object)가 되기 쉽다. 모든 규칙을 빨아들여 거대해지면, 결합을 푼 게 아니라 한 클래스로 옮겨담은 것뿐이다.
  • 상호작용이 단순하거나 위젯이 둘뿐이면 도입하지 말 것. 직접 참조가 더 읽기 쉽다.
  • 중재자가 커지면 영역별로 쪼개거나, 단순 통지로 충분한 부분은 Observer로 분리한다.

스스로 점검

1. 위젯끼리 직접 참조하면 의존선이 왜 폭발하나?

각 위젯이 반응해야 할 다른 위젯을 모두 알아야 하므로, N개 위젯이면 최대 N×N 의존이 생긴다. 중재자를 두면 각 위젯은 중재자 하나만 알아 N×1로 준다.

2. Observer와 Mediator를 가르는 가장 명확한 기준은?

방향과 로직 위치. Observer는 일대다 브로드캐스트(발행자는 구독자를 모름), Mediator는 다대다 조율(중재자가 동료를 다 알고 규칙을 집중 관리). “뿌리기”면 Observer, “지휘”면 Mediator.

3. Mediator를 잘못 쓰면 어떤 냄새가 나나?

중재자가 모든 규칙을 빨아들여 거대한 갓 오브젝트가 된다. 결합을 푼 게 아니라 한 곳으로 옮겨담은 것이라, 영역별로 분리하거나 일부를 Observer로 빼야 한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.