목차
서로 직접 얽혀 N:N이 된다
채팅방을 만든다고 하자. 참가자가 메시지를 보내면 방 안의 다른 참가자들에게 전해져야 한다. 각 참가자가 서로를 직접 참조하게 짜면 이렇게 된다.
public class User {
private final List<User> others; // 나 말고 모두를 안다
public void send(String msg) {
for (User u : others) {
u.receive(msg); // 하나하나 직접 전한다
}
}
}참가자가 셋이면 서로를 아는 연결이 여섯 가닥, 넷이면 열두 가닥이다. 참가자 수가 늘수록 연결은 제곱으로 불어난다. 이게 N:N 얽힘이다. 한 명이 들어오고 나갈 때마다 다른 모두의 others 목록을 손대야 하고, 메시지를 로그로 남기거나 특정 사용자를 차단하는 규칙을 넣으려면 이 규칙이 모든 User에 흩어져 박힌다.
중재자 = 소통을 한곳으로
미디에이터 패턴이 이걸 푼다.
객체들이 서로를 직접 참조하지 않게 하고, 중재자(Mediator) 하나를 세워 그리로만 소통하게 한다.
참가자는 다른 참가자를 모른다. 아는 건 방(중재자)뿐이다. “이 메시지 보내줘”라고 방에 알리면, 누구에게 어떻게 전할지는 방이 판단한다. 서로가 서로를 알던 그물이 중재자를 중심으로 한 별 모양으로 바뀐다.
Mediator 인터페이스
먼저 중재자가 무엇을 받아줄지 정한다.
public interface ChatMediator {
void send(String msg, User sender); // 보낸 사람과 메시지를 받는다
void join(User user); // 참가자를 등록한다
}참가자가 방에 알리는 통로는 이 send 하나다. 참가자는 이 인터페이스 너머에 누가 몇 명 있는지 알 필요가 없다.
컴포넌트는 중재자만 안다
참가자는 이제 다른 참가자 목록을 갖지 않는다. 중재자 하나만 쥔다.
public class User {
private final String name;
private final ChatMediator room; // 방만 안다
public User(String name, ChatMediator room) {
this.name = name;
this.room = room;
}
public void send(String msg) {
room.send(msg, this); // 방에 알릴 뿐
}
public void receive(String msg) {
System.out.println(name + " 받음: " + msg);
}
}send는 방에 넘기고 끝이다. 다른 참가자를 향한 반복문이 사라졌다. 참가자가 아는 세계는 room 하나로 좁아진다.
중재자가 조율한다
전달의 책임은 통째로 중재자로 들어간다.
public class ChatRoom implements ChatMediator {
private final List<User> users = new ArrayList<>();
@Override
public void join(User user) {
users.add(user);
}
@Override
public void send(String msg, User sender) {
for (User u : users) {
if (u != sender) { // 보낸 사람 빼고
u.receive(msg);
}
}
}
}“보낸 사람은 빼고 나머지에게” 같은 규칙도, 나중에 붙일 로그나 차단도 전부 ChatRoom 한 곳에 모인다. 규칙을 바꾸려면 여기만 고친다.
결합이 준다
값어치는 결합에서 나온다. 참가자들끼리 알던 N:N 연결이 사라지고, 각자 중재자만 아는 N:1로 바뀐다.
참가자를 추가하거나 빼도 다른 참가자는 아무 영향이 없다. 방에 join만 하면 된다. 전달 규칙이 바뀌어도 참가자 코드는 그대로다. 서로 몰라도 되니, 하나를 바꿀 때 딸려 흔들리던 것들이 조용해진다.
퍼사드와 무엇이 다른가
둘 다 “여럿 앞에 하나를 세운다”라 헷갈리지만 방향이 다르다.
- 퍼사드는 복잡한 하위 시스템에 단방향 창구를 낸다. 클라이언트가 창구를 부르고, 창구가 안쪽을 부른다. 안쪽 객체들은 창구가 있는지도 모른다.
- 중재자는 동료들 사이 양방향 조율이다. 각 동료가 중재자에게 알리고, 중재자가 다시 동료들을 부른다. 동료들은 서로 대신 중재자를 향한다.
한 줄로 줄이면, 퍼사드는 “밖에서 안으로 들어가는 쉬운 문”, 중재자는 “서로를 안 보게 가운데 세운 교환대”다.
실무: 채팅과 이벤트 버스
실무에서 중재자는 이벤트 버스로 흔히 나타난다. 컴포넌트가 서로를 직접 부르는 대신 버스에 이벤트를 올리면, 버스가 관심 있는 컴포넌트에 전달한다. 발신자와 수신자가 서로를 모른 채 버스만 아는 것이 그대로 중재자다. Spring의 ApplicationEventPublisher와 @EventListener도 이 발상이다. 이벤트를 발행하는 쪽은 누가 받는지 모른다.
주의도 있다. 소통을 다 몰아넣으면 이번엔 중재자가 비대해진다. 온갖 조율 규칙이 한 클래스에 쌓여 “신 객체(God Object)“가 되면, 얽힘을 참가자들에서 걷어다 중재자로 옮겨 담았을 뿐이 된다. 중재자도 한 종류의 조율 단위로 쪼개야 한다.
정리
| 중재자란 | 객체들이 서로 대신 중재자를 거쳐 소통하게 한다 |
| 문제 | 서로 직접 참조하면 N:N으로 얽혀 하나 바꾸면 다 흔들린다 |
| 고침 | 소통을 중재자 한곳으로 몰아 N:N을 N:1로 바꾼다 |
| 성질 | 각 컴포넌트는 서로를 모르고 중재자만 안다 |
| 효과 | 결합이 줄어 추가·제거·규칙 변경이 서로에게 안 번진다 |
| 퍼사드와 | 퍼사드=단방향 창구, 중재자=동료 사이 양방향 조율 |
서로를 직접 참조하며 얽히는 대신, 가운데 교환대를 하나 세워 소통을 그리로 모으는 것 - 그게 중재자다.