← Posts

GoF · Behavioral · Mediator

Mediator

객체들이 서로 직접 참조하며 얽히지 않도록, 중재자를 하나 세워 소통을 그리로 몰아주는 행위 패턴.

목차
  1. 서로 직접 얽혀 N:N이 된다
  2. 중재자 = 소통을 한곳으로
  3. Mediator 인터페이스
  4. 컴포넌트는 중재자만 안다
  5. 중재자가 조율한다
  6. 결합이 준다
  7. 퍼사드와 무엇이 다른가
  8. 실무: 채팅과 이벤트 버스
  9. 정리

서로 직접 얽혀 N:N이 된다

채팅방을 만든다고 하자. 참가자가 메시지를 보내면 방 안의 다른 참가자들에게 전해져야 한다. 각 참가자가 서로를 직접 참조하게 짜면 이렇게 된다.

java
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 인터페이스

먼저 중재자가 무엇을 받아줄지 정한다.

java
public interface ChatMediator {
    void send(String msg, User sender);   // 보낸 사람과 메시지를 받는다
    void join(User user);                 // 참가자를 등록한다
}

참가자가 방에 알리는 통로는 이 send 하나다. 참가자는 이 인터페이스 너머에 누가 몇 명 있는지 알 필요가 없다.

컴포넌트는 중재자만 안다

참가자는 이제 다른 참가자 목록을 갖지 않는다. 중재자 하나만 쥔다.

java
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 하나로 좁아진다.

중재자가 조율한다

전달의 책임은 통째로 중재자로 들어간다.

java
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);
            }
        }
    }
}
diagramdiagram

“보낸 사람은 빼고 나머지에게” 같은 규칙도, 나중에 붙일 로그나 차단도 전부 ChatRoom 한 곳에 모인다. 규칙을 바꾸려면 여기만 고친다.

결합이 준다

값어치는 결합에서 나온다. 참가자들끼리 알던 N:N 연결이 사라지고, 각자 중재자만 아는 N:1로 바뀐다.

참가자를 추가하거나 빼도 다른 참가자는 아무 영향이 없다. 방에 join만 하면 된다. 전달 규칙이 바뀌어도 참가자 코드는 그대로다. 서로 몰라도 되니, 하나를 바꿀 때 딸려 흔들리던 것들이 조용해진다.

퍼사드와 무엇이 다른가

둘 다 “여럿 앞에 하나를 세운다”라 헷갈리지만 방향이 다르다.

  • 퍼사드는 복잡한 하위 시스템에 단방향 창구를 낸다. 클라이언트가 창구를 부르고, 창구가 안쪽을 부른다. 안쪽 객체들은 창구가 있는지도 모른다.
  • 중재자동료들 사이 양방향 조율이다. 각 동료가 중재자에게 알리고, 중재자가 다시 동료들을 부른다. 동료들은 서로 대신 중재자를 향한다.

한 줄로 줄이면, 퍼사드는 “밖에서 안으로 들어가는 쉬운 문”, 중재자는 “서로를 안 보게 가운데 세운 교환대”다.

실무: 채팅과 이벤트 버스

실무에서 중재자는 이벤트 버스로 흔히 나타난다. 컴포넌트가 서로를 직접 부르는 대신 버스에 이벤트를 올리면, 버스가 관심 있는 컴포넌트에 전달한다. 발신자와 수신자가 서로를 모른 채 버스만 아는 것이 그대로 중재자다. Spring의 ApplicationEventPublisher@EventListener도 이 발상이다. 이벤트를 발행하는 쪽은 누가 받는지 모른다.

주의도 있다. 소통을 다 몰아넣으면 이번엔 중재자가 비대해진다. 온갖 조율 규칙이 한 클래스에 쌓여 “신 객체(God Object)“가 되면, 얽힘을 참가자들에서 걷어다 중재자로 옮겨 담았을 뿐이 된다. 중재자도 한 종류의 조율 단위로 쪼개야 한다.

정리

중재자란객체들이 서로 대신 중재자를 거쳐 소통하게 한다
문제서로 직접 참조하면 N:N으로 얽혀 하나 바꾸면 다 흔들린다
고침소통을 중재자 한곳으로 몰아 N:N을 N:1로 바꾼다
성질각 컴포넌트는 서로를 모르고 중재자만 안다
효과결합이 줄어 추가·제거·규칙 변경이 서로에게 안 번진다
퍼사드와퍼사드=단방향 창구, 중재자=동료 사이 양방향 조율

서로를 직접 참조하며 얽히는 대신, 가운데 교환대를 하나 세워 소통을 그리로 모으는 것 - 그게 중재자다.

관련 글