스택큐힙리스트

Mediator 패턴: 복잡한 객체 의존을 한 방에 정리하기 본문

개발

Mediator 패턴: 복잡한 객체 의존을 한 방에 정리하기

스택큐힙리스트 2025. 7. 29. 09:04
반응형

UI 컴포넌트·모듈·마이크로서비스가 서로 직접 호출을 시작하면, 어느 순간 “누가 누구를 부르는지” 알 수 없는 스파게티 의존 그래프가 됩니다.
Mediator 패턴은 “중재자”-객체를 사이에 두어 모든 통신을 중앙 집중화합니다. 콜리그(Colleague)들은 중재자에게만 말하고, 중재자가 대신 다른 콜리그에게 메시지를 전달합니다. 결과적으로 각 객체는 서로를 전혀 모른 채 협력할 수 있고, 관계 변경도 중재자만 수정하면 됩니다.

언제 쓰면 좋은가?

  • 이벤트 버스: 게시자와 구독자가 직접 연결되지 않고 중앙 EventBus(Mediator)를 통해 소통.
  • 알림 시스템: 다양한 채널(SMS, 이메일, 앱 푸시)을 하나의 NotificationHub(Mediator)가 조정.
  • 상태 머신: 여러 상태 객체가 TransitionManager(Mediator)를 통해 전이 로직을 공유.

구조 한눈에 보기

  • Mediator 인터페이스: notify(sender, event)
  • ConcreteMediator: 이벤트별 라우팅 로직 보유
  • Colleague: send(event) → mediator 로 위임, receive() 로 콜백

Kotlin 미니 예제 – ChatEventBus

interface Mediator {
    fun notify(sender: Colleague, event: String, payload: Any? = null)
}
class ChatEventBus : Mediator {
    private val colleagues = mutableListOf<Colleague>()
    fun register(c: Colleague) { colleagues += c }
    override fun notify(sender: Colleague, event: String, payload: Any?) {
        colleagues.filter { it != sender }
                  .forEach { it.receive(event, payload) }
    }
}
abstract class Colleague(private val bus: Mediator) {
    fun send(event: String, payload: Any? = null) = bus.notify(this, event, payload)
    abstract fun receive(event: String, payload: Any?)
}
class User(bus: Mediator, private val name: String) : Colleague(bus) {
    override fun receive(event: String, payload: Any?) =
        println("[$name] $event : $payload")
}
fun main() {
    val bus = ChatEventBus()
    val a = User(bus, "철수").also { bus.register(it) }
    val b = User(bus, "영희").also { bus.register(it) }
    a.send("MSG", "안녕 👋")
}

이벤트 버스를 Mediator로 구현해 두 사용자 객체가 서로를 모른 채 메시지를 주고받습니다. 중앙 버스를 다른 구현체로 교체하면 로직 전체를 흔들지 않고 기능을 확장할 수 있습니다.

JUnit 테스트 스케치

@Test
fun `버스가 메시지를 중계한다`() {
    val bus = ChatEventBus()
    val received = mutableListOf<String>()
    val a = object : Colleague(bus) {
        override fun receive(event: String, payload: Any?) { /* no-op */ }
    }
    val b = object : Colleague(bus) {
        override fun receive(event: String, payload: Any?) {
            if (event == "PING") received += "${payload}"
        }
    }
    bus.register(a); bus.register(b)
    a.send("PING", "hello")
    assertEquals(listOf("hello"), received)
}

장점 vs 단점

  • 👍 결합도↓: Colleague 간 직접 참조 제거 → 테스트·유지보수 용이
  • 👍 단일 행동 지점: 복잡한 비즈니스 흐름을 Mediator 하나에 집중
  • 👎 Mediator 비대화: 모든 규칙이 몰리면 거대한 “신-객체”가 될 위험 → 서브-중재자 분할·이벤트 서브 타입 도입으로 완화

한 줄 요약

Mediator 패턴은 “다대다 의존의 골칫거리”“하나의 중재자”로 수렴시켜 코드의 복잡도를 기하급수적으로 낮춰 줍니다.

반응형
Comments