중재자 패턴 (Mediator Pattern)

중재자 패턴(Mediator Pattern)은 객체 지향 설계에서 객체 간의 상호작용을 중앙에서 관리할 수 있도록 하는 행동(Behavioral) 디자인 패턴입니다. 이 패턴은 객체들이 직접 서로 통신하는 대신, 중재자(Mediator)라는 객체를 통해 통신하게 하여 객체 간의 결합도를 줄이는 데 목적이 있습니다. 중재자 패턴을 사용하면 시스템의 복잡성을 줄이고, 객체 간의 의존성을 최소화할 수 있습니다.

“소통의 중심, 조화의 열쇠. 하나로 연결된 개체들의 독립된 춤.”

중재자 패턴(Mediator Pattern)은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:

  1. 객체 간의 복잡한 상호작용을 중앙에서 관리하고 싶을 때:
    • 시스템 내 여러 객체가 서로 복잡하게 상호작용하는 경우, 이러한 상호작용을 중앙에서 관리하면 코드의 복잡성을 줄일 수 있다. 중재자 패턴을 사용하면 객체들이 직접 서로 통신하지 않고, 중재자를 통해 간접적으로 통신할 수 있어 상호작용의 복잡성이 감소한다. 채팅 애플리케이션에서 여러 사용자가 메시지를 주고받을 때, 모든 메시지 통신을 채팅방 중재자가 관리하여 사용자 간의 상호작용을 단순화할 수 있다.
  2. 객체 간의 결합도를 낮추고 싶을 때:
    • 객체들이 서로 직접 참조하고 통신하는 경우, 시스템의 결합도가 높아져 유지보수와 확장이 어려워질 수 있다. 중재자 패턴을 사용하면 객체 간의 직접적인 의존성을 줄이고, 대신 중재자를 통해 통신하므로 결합도가 낮아진다. GUI 애플리케이션에서 여러 UI 컴포넌트가 서로의 상태에 의존하는 경우, 중재자를 통해 상호작용을 조정하여 컴포넌트 간의 결합도를 줄일 수 있다.
  3. 다수의 객체가 서로 다른 객체들과 자주 통신해야 할 때:
    • 여러 객체가 서로 자주 통신해야 하는 시스템에서는 객체 간의 복잡한 상호작용이 발생할 수 있다. 중재자 패턴을 사용하면 이러한 복잡한 통신을 중재자에 캡슐화하여 관리할 수 있으며, 시스템의 구조를 더 명확하게 만들 수 있다. 항공 관제 시스템에서 여러 항공기와 관제탑 간의 통신을 중앙에서 관리하는 중재자를 통해 상호작용을 조정할 수 있다.
  4. 통신 로직을 캡슐화하여 변경 가능성을 줄이고 싶을 때:
    • 시스템에서 통신 로직이 자주 변경되거나, 다양한 방식으로 통신이 이루어져야 하는 경우, 중재자 패턴을 사용하면 통신 로직을 중재자에 캡슐화하여 쉽게 변경할 수 있다. 이로 인해 시스템의 변경이 용이해지고 유지보수성이 향상된다. 소프트웨어에서 플러그인 시스템을 구현할 때, 각 플러그인이 서로 간접적으로 상호작용하도록 중재자를 사용하여 플러그인 간의 통신 로직을 캡슐화할 수 있다.
  5. 객체들이 서로 몰라도 되는 독립적인 관계를 유지하고 싶을 때:
    • 객체들이 서로의 존재를 몰라도 되는 독립적인 관계를 유지하려면, 중재자를 통해 상호작용을 관리하는 것이 유리하다. 각 객체는 중재자만 알면 되므로, 시스템의 독립성이 높아지고 유지보수가 용이해진다. 금융 시스템에서 여러 금융 기관이 거래를 중재자를 통해 처리하도록 하여, 각 기관이 다른 기관의 세부 사항을 알 필요 없이 거래를 처리할 수 있다.

장점

  1. 객체 간의 결합도 감소 * 중재자 패턴을 사용하면 객체들이 직접 서로 통신하지 않고, 중재자를 통해 간접적으로 통신하게 된다. 이로 인해 객체 간의 결합도가 크게 줄어든다. 결합도가 낮아지면 각 객체가 독립적으로 존재할 수 있으며, 변경이 일어날 때 다른 객체에 미치는 영향이 적어진다.
  2. 복잡한 상호작용의 단순화 * 여러 객체 간의 복잡한 상호작용을 중앙의 중재자가 관리하게 되면, 각 객체는 자신의 역할에만 집중할 수 있다. 객체들이 상호작용하는 방법을 중재자가 관리하기 때문에, 복잡한 상호작용이 단순화된다.
  3. 통신 로직의 캡슐화 * 중재자 패턴은 객체 간의 통신 로직을 중재자에게 캡슐화하여 관리한다. 이를 통해 통신 로직을 한 곳에서 집중적으로 제어할 수 있으며, 통신 로직을 수정하거나 확장할 때도 중재자만 변경하면 되므로 코드 수정 범위가 줄어든다.
  4. 유연한 확장 가능성 * 중재자 패턴을 사용하면 새로운 객체를 추가하거나 기존 객체를 제거하는 작업이 쉬워진다. 새로운 객체를 추가할 때, 기존 객체를 수정할 필요 없이 중재자에게만 추가하면 되므로 시스템의 확장성이 높아진다.
  5. 시스템의 유지보수성 향상 * 중재자 패턴은 복잡한 상호작용을 중앙에서 관리하기 때문에, 시스템의 유지보수가 용이해진다. 통신 로직이 한 곳에 집중되어 있기 때문에, 문제가 발생했을 때 이를 추적하고 해결하기가 더 쉽다.

단점

  1. 중재자의 복잡성 증가 * 중재자 패턴을 사용하면 모든 상호작용이 중재자에게 집중되므로, 중재자가 복잡해질 수 있다. 중재자가 너무 많은 책임을 지게 되면, 코드가 복잡해지고 유지보수가 어려워질 수 있다. 중재자는 시스템의 중요한 부분이 되므로, 복잡성이 높아질수록 오류 발생 가능성도 증가한다.
  2. 성능 문제 * 중재자가 모든 통신을 처리하므로, 중재자가 성능의 병목 현상을 일으킬 수 있다. 특히, 많은 객체들이 자주 통신해야 하는 시스템에서는 중재자가 과부하될 가능성이 있다. 모든 상호작용이 중재자를 거치기 때문에, 성능 저하가 발생할 수 있다.
  3. 중재자에 지나치게 의존 * 중재자 패턴을 사용하면 시스템의 모든 상호작용이 중재자에 의존하게 된다. 중재자가 복잡해지거나 제대로 관리되지 않으면, 시스템 전체의 안정성이 위협받을 수 있다. 중재자에 너무 많은 기능이 집중되면 유지보수가 어려워질 수 있다.
  4. 시스템 이해의 어려움 * 중재자 패턴을 사용하면 모든 상호작용이 중재자에게 집중되므로, 시스템의 동작 방식을 이해하는 것이 어려워질 수 있다. 특히, 중재자가 복잡한 로직을 포함하고 있을 경우, 시스템의 전체적인 흐름을 이해하기가 어렵다.
  5. 단일 장애 지점(Single Point of Failure) 위험 * 중재자가 시스템의 모든 상호작용을 관리하므로, 중재자가 실패하면 시스템 전체가 작동하지 않을 수 있다. 중재자에 의존하는 만큼, 중재자의 안정성을 높이는 것이 중요하지만, 그만큼 시스템이 중재자에 종속되게 된다.

중재자 패턴(Mediator Pattern)을 사용한 예제:

ChatRoom이라는 중재자가 있고, 여러 User 객체들이 이 중재자를 통해 서로 메시지를 주고받는다. 각 사용자는 ChatRoom을 통해 다른 사용자에게 메시지를 보낸다.

import java.util.ArrayList;
import java.util.List;

// Colleague 인터페이스
interface User {
    void send(String message);
    void receive(String message);
}

// Concrete Colleague 클래스: 구체적인 사용자
class ChatUser implements User {
    private String name;
    private ChatRoomMediator mediator;

    public ChatUser(String name, ChatRoomMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    @Override
    public void send(String message) {
        System.out.println(this.name + " sends: " + message);
        mediator.showMessage(this, message);
    }

    @Override
    public void receive(String message) {
        System.out.println(this.name + " receives: " + message);
    }

    public String getName() {
        return name;
    }
}

// Mediator 인터페이스
interface ChatRoomMediator {
    void showMessage(User user, String message);
}

// Concrete Mediator 클래스: 구체적인 중재자
class ChatRoom implements ChatRoomMediator {
    private List<User> users = new ArrayList<>();

    public void addUser(User user) {
        users.add(user);
    }

    @Override
    public void showMessage(User user, String message) {
        for (User u : users) {
            // 메시지를 보낸 사용자 자신에게는 메시지를 다시 전달하지 않음
            if (u != user) {
                u.receive(user.getName() + ": " + message);
            }
        }
    }
}

// 클라이언트 코드
public class MediatorPatternExample {
    public static void main(String[] args) {
        ChatRoom chatRoom = new ChatRoom();

        User user1 = new ChatUser("Alice", chatRoom);
        User user2 = new ChatUser("Bob", chatRoom);
        User user3 = new ChatUser("Charlie", chatRoom);

        chatRoom.addUser(user1);
        chatRoom.addUser(user2);
        chatRoom.addUser(user3);

        user1.send("Hello everyone!");
        user2.send("Hi Alice!");
        user3.send("Hey Bob, how are you?");
    }
}

코드 설명

  1. User 인터페이스 (Colleague): * User 인터페이스는 메시지를 보내고 받을 수 있는 메서드를 정의한다. 모든 사용자는 이 인터페이스를 구현하여 중재자와 상호작용할 수 있다.
  2. ChatUser 클래스 (Concrete Colleague): * ChatUser 클래스는 User 인터페이스를 구현한 구체적인 사용자 클래스이다. 이 클래스는 메시지를 보내거나 받을 때 중재자(채팅방)를 통해 다른 사용자들과 통신한다.
  3. ChatRoomMediator 인터페이스 (Mediator): * ChatRoomMediator 인터페이스는 중재자가 제공해야 하는 메서드를 정의한다. 중재자는 메시지를 받아서 적절한 사용자들에게 전달하는 역할을 한다.
  4. ChatRoom 클래스 (Concrete Mediator): * ChatRoom 클래스는 ChatRoomMediator 인터페이스를 구현한 구체적인 중재자 클래스이다. 이 클래스는 사용자 목록을 관리하고, 메시지를 적절한 사용자들에게 전달한다.
  5. 클라이언트 코드: * 클라이언트는 ChatRoom 객체(중재자)를 생성하고, 여러 ChatUser 객체(동료 객체)를 생성하여 채팅방에 추가한다. 각 사용자가 메시지를 보내면, 중재자가 해당 메시지를 받아 다른 사용자들에게 전달한다.

관련패턴

  1. 옵저버 패턴(Observer Pattern)
    • 옵저버 패턴과 중재자 패턴은 모두 객체 간의 상호작용을 관리하는 데 중점을 둔다. 옵저버 패턴은 객체 간의 일대다 관계에서 상태 변화를 감지하고 통지하는 구조를 제공하며, 중재자 패턴은 다수의 객체 간의 상호작용을 중앙에서 관리한다. 중재자 패턴은 옵저버 패턴을 구현할 때 유용할 수 있다. 예를 들어, 중재자가 옵저버 역할을 하여 상태 변화를 감지하고 적절한 객체에게 통지할 수 있다.
  2. 커맨드 패턴(Command Pattern)
    • 커맨드 패턴은 요청을 객체로 캡슐화하여, 클라이언트가 요청을 실행할 수 있도록 하는 패턴이다. 중재자 패턴과 함께 사용하여, 중재자가 다양한 커맨드를 관리하거나 조정할 수 있다. 중재자 패턴을 사용하면 여러 커맨드를 중앙에서 관리하고, 각 커맨드를 적절한 객체에 전달하여 실행할 수 있습니다. 이는 시스템의 확장성을 높이고, 커맨드의 실행 순서나 조건을 쉽게 관리할 수 있다.
  3. 퍼사드 패턴(Facade Pattern)
    • 퍼사드 패턴은 복잡한 서브시스템에 대한 단순한 인터페이스를 제공하여, 클라이언트가 서브시스템을 쉽게 사용할 수 있도록 하는 패턴이다. 중재자 패턴과 퍼사드 패턴은 모두 시스템의 복잡성을 줄이는 역할을 한다. 퍼사드 패턴은 객체 간의 상호작용을 단순화하기 위해 중재자 패턴과 함께 사용할 수 있다. 중재자는 복잡한 객체 간의 상호작용을 관리하고, 퍼사드는 이 상호작용에 대해 단순한 인터페이스를 제공할 수 있다.
  4. 플라이웨이트 패턴(Flyweight Pattern)
    • 플라이웨이트 패턴은 메모리를 절약하기 위해 동일한 객체를 공유하는 패턴이다. 중재자 패턴을 사용하면 여러 객체가 동일한 중재자를 공유할 수 있으며, 플라이웨이트 패턴을 활용하여 중재자의 상태를 공유할 수 있다. 중재자 패턴과 플라이웨이트 패턴을 결합하면, 메모리 사용을 최적화하면서도 객체 간의 상호작용을 효과적으로 관리할 수 있다.
  5. 템플릿 메서드 패턴(Template Method Pattern)
    • 템플릿 메서드 패턴은 알고리즘의 구조를 정의하고, 알고리즘의 일부 단계를 서브클래스에서 구현하도록 하는 패턴이다. 중재자 패턴은 템플릿 메서드 패턴을 사용하여 상호작용의 기본 구조를 정의하고, 세부적인 상호작용 로직을 서브클래스에서 구현할 수 있다. 이 조합을 통해 중재자 패턴의 유연성을 높이고, 다양한 상호작용 시나리오를 쉽게 관리할 수 있다.
  6. 프로토타입 패턴(Prototype Pattern)
    • 프로토타입 패턴은 새로운 객체를 생성할 때, 기존 객체를 복사하여 생성하는 패턴입니다. 중재자 패턴에서 관리하는 객체들이 동적으로 생성되거나 복제될 필요가 있을 때, 프로토타입 패턴을 활용할 수 있다. 중재자 패턴을 사용하는 시스템에서 동일한 객체가 반복적으로 생성될 필요가 있는 경우, 프로토타입 패턴을 사용하여 효율적으로 객체를 생성하고 관리할 수 있다.
  7. 체인 오브 책임 패턴(Chain of Responsibility Pattern)
    • 체인 오브 책임 패턴은 요청을 처리할 객체들이 체인 형태로 연결되어 있으며, 요청이 체인을 따라 전달되다가 적절한 객체가 처리하는 패턴이다. 중재자 패턴은 객체 간의 상호작용을 중앙에서 관리하는 반면, 체인 오브 책임 패턴은 상호작용을 여러 객체에 분산시킨다. 중재자 패턴과 체인 오브 책임 패턴을 결합하면, 특정 요청에 대한 처리 로직을 분산시키면서도, 전체적인 상호작용을 중앙에서 관리할 수 있다.