미디에이터 패턴, 얽힌 객체 통신을 중재자로 정리하는 법
미디에이터 패턴은 객체들이 서로 직접 참조하는 대신 중재자 하나를 거쳐 통신하게 만드는 GoF 행동 패턴이다. 다대다로 얽힌 의존을 낮추는 구조와 자바 예제, 옵저버·퍼사드와의 차이, .NET MediatR 사례까지 정리했다.
목차
미디에이터 패턴(Mediator Pattern)은 여러 객체가 서로를 직접 참조하며 통신하는 대신, 중재자(mediator)라는 객체 하나를 거쳐서만 통신하게 만드는 설계 방법이다. 각 객체가 다른 객체의 존재를 모른 채 중재자에게만 의존하게 해서, 얽히고설킨 의존 관계를 한곳으로 모으는 것이 목적이다. GoF(Gang of Four)가 1994년 책 『Design Patterns』에서 정리한 23개 디자인 패턴 중 하나이며, 객체의 행동과 상호작용을 다루는 행동 패턴(behavioral pattern)으로 분류된다.
이 글은 특정 언어나 프레임워크에 매이지 않는 개념과 구조를 먼저 설명하고, 자바 스타일 예제 코드와 비교 표, 그리고 .NET 진영의 실제 라이브러리 사례까지 다룬다. 문법 세부보다 "언제 이 패턴을 꺼내 들고 언제 피해야 하는가"에 무게를 둔 실무 관점의 글이다.
미디에이터 패턴은 어떤 문제를 푸는가
서로 통신해야 하는 객체가 늘어날수록 심해지는 '다대다(N:N) 의존' 문제를 푼다. 객체들이 서로를 직접 참조하면 연결선이 기하급수로 늘어난다. 객체가 n개일 때 모두가 모두와 통신하면 최악의 경우 연결이 n(n-1)/2개까지 생긴다. 객체 5개면 10개, 10개면 45개다. 여기서 새 객체 하나를 더 넣으면 그 객체뿐 아니라 이미 연결돼 있던 기존 객체들까지 코드를 손봐야 한다.
미디에이터는 이 그물망을 별(star) 모양으로 바꾼다. 모든 객체가 중재자 하나만 바라보게 하면 연결 수가 n개로 줄고, 객체를 추가할 때도 중재자에 등록만 하면 된다. 흔히 드는 비유가 항공 관제탑이다. 여러 비행기가 서로 무전으로 직접 조율하면 혼란스럽지만, 모두가 관제탑 하나와만 교신하면 흐름이 통제된다. 여러 사용자가 오가는 채팅방도 같은 구조다. 사용자끼리 직접 메시지를 주고받는 대신 방(중재자)이 메시지를 받아 나머지에게 뿌린다.
패턴의 구조: 중재자와 동료 객체
미디에이터 패턴은 네 가지 역할로 이뤄진다. 리팩터링 구루의 디자인 패턴 정리에 따르면 구성은 다음과 같다.
- Mediator(중재자 인터페이스): 동료 객체들이 중재자에게 알림을 보낼 때 쓰는 통신 규약을 정의한다.
- ConcreteMediator(구체 중재자): 동료 객체들을 알고 있으면서, 한 객체의 알림을 받아 어느 객체에게 무엇을 시킬지 실제 조율 로직을 담는다.
- Colleague/Component(동료 객체): 자신을 조율하는 중재자만 참조할 뿐 다른 동료의 존재는 모른다.
- ConcreteColleague(구체 동료): 실제 업무를 수행하는 객체로, 무언가 할 일이 생기면 다른 객체를 직접 부르지 않고 중재자에게 알린다.
핵심은 '동료는 중재자만 알고, 동료끼리는 서로 모른다'는 규칙이다. 덕분에 상호작용 규칙을 바꾸고 싶으면 동료 객체들은 그대로 두고 중재자만 교체하면 된다. 여러 구성요소를 중앙에서 조율한다는 발상은, 노드와 엣지로 실행 흐름을 제어하는 AI 에이전트 그래프 오케스트레이션처럼 조정자를 두고 흐름을 통제하는 설계와도 통한다.
코드로 보는 미디에이터
채팅방 예제로 보면 구조가 한눈에 들어온다. 아래 자바 코드에서 User는 다른 User를 전혀 참조하지 않는다. 오직 중재자인 ChatRoom에만 메시지를 던지고, 누구에게 전달할지는 중재자가 결정한다.
// 중재자 인터페이스
interface ChatMediator {
void send(String message, User sender);
}
// 동료(Colleague) 공통 추상 클래스
abstract class User {
protected final ChatMediator mediator;
protected final String name;
User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
void send(String message) { // 다른 사용자를 직접 참조하지 않는다
mediator.send(message, this);
}
abstract void receive(String message);
}
// 구체 중재자
class ChatRoom implements ChatMediator {
private final List<User> users = new ArrayList<>();
void register(User user) {
users.add(user);
}
public void send(String message, User sender) {
for (User user : users) {
if (user != sender) { // 보낸 사람만 빼고 전달
user.receive(message);
}
}
}
}새 사용자를 100명 추가해도 기존 User 코드는 한 줄도 바뀌지 않는다. ChatRoom.register()로 등록만 하면 끝이다. 만약 '차단 목록에 있는 사용자에게는 안 보낸다'는 규칙을 넣고 싶어도, 수정 지점은 ChatRoom.send() 한 곳뿐이다. 이렇게 상호작용 규칙이 한 클래스에 모여 있으면 변경 범위를 좁게 통제할 수 있다.

직접 통신과 무엇이 달라지나
같은 기능을 중재자 없이 짜면 각 객체가 상대 목록을 직접 들고 있어야 한다. 통신 규칙이 여러 클래스에 흩어지고, 객체 하나를 고치면 그와 엮인 객체들이 연쇄로 흔들린다. 아래 표가 두 방식의 차이를 정리한 것이다.
| 구분 | 중재자 없는 직접 통신 | 미디에이터 도입 |
|---|---|---|
| 객체 간 참조 | 서로가 서로를 직접 참조 | 각자 중재자 하나만 참조 |
| 연결 수(객체 n개) | 최악 n(n-1)/2개 | n개 |
| 새 객체 추가 | 기존 객체들도 함께 수정 | 중재자에 등록만 |
| 상호작용 규칙 위치 | 여러 객체에 흩어짐 | 중재자 한 곳에 모임 |
주의할 점은 이 표가 '중재자가 항상 옳다'는 뜻은 아니라는 것이다. 연결을 중재자로 모으면 개별 동료 객체는 단순해지지만, 그만큼 복잡성이 중재자 한곳으로 옮겨 갈 뿐이다. 이 균형을 어떻게 볼지는 뒤에서 다시 짚는다.
옵저버·퍼사드 패턴과 어떻게 구분하나
셋 다 객체 사이를 정리하지만 목적이 다르다. 미디에이터는 다대다 통신을 중재자로 중앙화하고, 옵저버는 한 객체의 상태 변화를 여러 구독자에게 일대다로 통지하며, 퍼사드는 복잡한 서브시스템에 단순한 진입점 하나를 얹는다. 헷갈리기 쉬운 세 패턴을 표로 비교하면 다음과 같다.
| 패턴 | GoF 분류 | 핵심 목적 |
|---|---|---|
| 미디에이터 | 행동 | 객체 간 다대다 통신을 중재자로 중앙화 |
| 옵저버 | 행동 | 상태 변화를 구독자들에게 일대다로 통지 |
| 퍼사드 | 구조 | 복잡한 서브시스템에 단순한 진입점 제공 |
실전에서 특히 미디에이터와 옵저버가 자주 섞인다. 통지 방향으로 구분하면 쉽다. 옵저버는 '발행자 → 구독자들'로 한 방향 흐름이고, 미디에이터는 여러 동료가 중재자를 향해 양방향으로 알림을 주고받으며 중재자가 그때그때 다른 동료를 지목해 조율한다. 실제로 미디에이터 구현 안에서 동료 등록·해제에 옵저버 방식을 쓰기도 해서, 둘은 경쟁 관계라기보다 함께 엮이는 경우가 많다.
실무에서 어디에 쓰이나
가장 고전적인 예는 GUI 다이얼로그다. 체크박스 하나를 켜면 특정 버튼이 활성화되고, 목록에서 항목을 고르면 입력란이 채워지는 식의 위젯 간 연동 규칙을 각 위젯에 흩어 두면 유지보수가 어렵다. 이럴 때 다이얼로그를 중재자로 두고 위젯들의 상호작용 로직을 그 안에 모은다. 웹의 MVC 구조에서 컨트롤러가 뷰와 모델 사이를 조율하는 모습도 넓게 보면 같은 발상이다.
백엔드에서는 .NET 진영의 MediatR가 대표적이다. MediatR는 요청(request)과 처리기(handler)를 직접 연결하지 않고 중재자를 거치게 하는 인프로세스 메시징 라이브러리로, CQRS나 클린 아키텍처 구성에서 컨트롤러가 핸들러를 직접 알지 못하게 분리하는 데 쓰인다. MediatR를 만든 지미 보가드(Jimmy Bogard)는 자신의 블로그에서 2025년 7월 MediatR와 AutoMapper의 상용 에디션 출시를 발표했다. 무료 커뮤니티 티어를 유지하는 이중 라이선스 모델이며, 팀 규모에 따라 상용 라이선스가 필요하다는 것이 발표의 골자다. 상세 조건과 버전별 정책은 MediatR 공식 저장소에서 확인하는 편이 안전하다.
도입 전에 따져볼 트레이드오프
미디에이터의 최대 위험은 중재자가 '신의 객체(God Object)'로 비대해지는 것이다. 동료들의 상호작용 규칙을 전부 빨아들이다 보면, 처음엔 깔끔했던 중재자가 수백 줄짜리 분기문 덩어리로 자라 오히려 손대기 무서운 클래스가 된다. 결합도를 낮추려고 도입한 패턴이 결국 중재자에 대한 집중과 복잡성으로 갚아야 하는 셈이다.
판단 기준은 이렇게 잡으면 된다. 서로 통신하는 객체가 셋 이상이고 그 연결 규칙이 자주 바뀐다면 미디에이터가 값을 한다. 반대로 통신하는 객체가 둘 정도로 적거나 관계가 고정적이라면, 굳이 중간 계층을 끼우지 않고 직접 참조하는 편이 읽기 쉽다. 중재자를 도입하기로 했다면 조율 로직이 한 클래스에서 너무 커지지 않도록 역할을 잘게 쪼개고, 필요하면 중재자를 여러 개로 나누는 것도 방법이다. 패턴은 규칙이 아니라 도구다. 지금 코드의 통신 그물이 실제로 엉켜 있는지부터 확인한 뒤 꺼내 드는 게 순서다.





