객체들이 서로 직접 참조하지 않고 중재자를 통해 소통하게 하는 패턴.
무엇을 푸는가 — N:N 결합
Before 버튼 ↔ 체크박스 ↔ 입력창 ↔ 목록 (서로를 다 안다)
객체 n 개면 관계가 최대 n(n-1)/2
After 버튼 → 중재자 ← 체크박스
↑ ↑
입력창 목록 각자는 중재자만 안다 → 관계 n 개
interface Mediator { void notify(Component sender, String event); }
class DialogMediator implements Mediator {
private CheckBox agree; private Button submit;
public void notify(Component sender, String event) {
if (sender == agree && event.equals("change"))
submit.setEnabled(agree.isChecked()); // 상호작용 규칙이 여기 모인다
}
}
Observer와의 차이
-
Observer — 발행자 → 구독자 단방향 통지. 발행자는 구독자를 모른다
- '상태가 바뀌었다' 를 알린다
-
Mediator — 양방향 조율. 중재자가 참여자 모두를 안다
- '누가 무엇을 하면 누가 어떻게 반응한다' 는 규칙을 담는다
-
실무에서는 Mediator 를 Observer 로 구현하는 경우도 많다 (이벤트 버스)
대가 — 중재자가 God Object가 된다
모든 상호작용 규칙이 한 곳에 모이므로 참여자가 늘수록 중재자가 비대해진다
- 화면·기능 단위로 중재자를 나눈다
- 규칙이 단순하면 그냥 직접 참조가 낫다
실무에서
java.util.Timer // 태스크 실행 조율
ExecutorService // 작업 ↔ 스레드 중재
Spring ApplicationEventPublisher // 이벤트 버스형
MVC 의 Controller // 모델 ↔ 뷰 중재
면접 함정
- ❌ "Observer의 다른 이름" → 단방향 통지 vs 양방향 조율로 다르다.
- ❌ 무조건 도입 → 참여자가 적으면 중재자가 불필요한 간접층이 된다.
이벤트 버스로 구현하는 형태
@Component
class OrderService {
private final ApplicationEventPublisher publisher;
void place(Order o) { publisher.publishEvent(new OrderPlaced(o.getId())); }
}
@Component
class NotificationHandler {
@EventListener void on(OrderPlaced e) { ... }
}
- 장점 — 결합이 끊긴다 — 구독자를 추가해도 발행자를 안 고친다
- 대가 — 흐름이 코드에서 안 보인다 ("이 이벤트를 누가 받나" 를 찾기 어렵다)
- 트랜잭션 경계가 헷갈린다 (@TransactionalEventListener 필요)
언제 과설계인가
- 참여자가 2~3개다 → 직접 참조가 더 읽기 쉽다
- 상호작용 규칙이 단순하다 → 중재자가 빈 껍데기가 된다
- 흐름 추적이 중요한 도메인(결제·정산) → 간접층이 디버깅을 방해한다
비대화를 막는 법
화면·유스케이스 단위로 중재자를 나누고, 중재자는 조율만 하고 판단은 참여자가 하게 한다. 규칙이 늘면 규칙 객체로 다시 추출한다.