Mediator Pattern 정리

테사벨로그·2025년 10월 23일

Design Pattern

목록 보기
13/19

1. 왜 Mediator Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: 객체들이 서로 직접 통신하는 코드
public class ListBox {
    private EntryField entryField;
    private Button okButton;
    private Button cancelButton;
    
    public void onSelectionChanged() {
        // ListBox가 모든 다른 객체들을 알아야 함
        entryField.setText(getSelection());
        okButton.setEnabled(true);
        cancelButton.setEnabled(true);
    }
}

public class EntryField {
    private ListBox listBox;
    private Button okButton;
    
    public void onTextChanged() {
        // EntryField도 다른 객체들을 알아야 함
        listBox.selectItem(getText());
        okButton.setEnabled(!getText().isEmpty());
    }
}

문제점:

  • 객체들이 서로를 직접 참조 (강한 결합)
  • 관계가 복잡해질수록 코드 유지보수가 어려움
  • 새로운 객체를 추가하거나 관계를 변경하려면 여러 클래스 수정 필요
  • 객체 간 통신 흐름을 이해하기 어려움

2. Mediator Interface VS Colleague Interface

1. Mediator Interface

  • "객체들 간의 통신을 나를 통해 하세요"
  • 객체 간 통신을 중재하는 규격 정의
  • "coordinates" 관계 (Mediator는 Colleagues를 조정함)
public interface Mediator {
    void mediate();  // 중재 메서드
}

왜 Interface인가?

  • 다양한 상황(Dialog, 채팅방, 게임 등)에서 중재자가 필요할 수 있음
  • 각 상황마다 중재 로직은 다르지만, 중재자 역할은 동일
  • 중재 방식만 통일하면 됨

2. Colleague Class

  • "무언가 변경되면 Mediator에게 알려줘요"
  • Mediator를 참조하여 통신을 요청하는 규격
  • "communicates-through" 관계 (Colleague는 Mediator를 통해 통신)
public abstract class Colleague {
    protected Mediator mediator;
    
    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }
}

왜 Abstract Class/Interface인가?

  • ListBox, Button, TextField 등 다양한 UI 컴포넌트가 Colleague가 될 수 있음
  • 각자 다른 동작을 하지만 모두 Mediator를 통해 통신
  • Mediator는 Colleague의 구체적인 구현을 몰라도 됨

3. 왜 Abstract Class가 아닌 Interface인가?

Mediator는 Interface 사용

  1. 다양한 중재 방식

    // ✅ 가능: 다양한 Dialog마다 다른 중재 로직
    public class FontDialogDirector implements Mediator {
        // 폰트 다이얼로그 전용 중재 로직
    }
    
    public class FileDialogDirector implements Mediator {
        // 파일 다이얼로그 전용 중재 로직
    }
  2. 공통 구현이 없음

    • 각 Mediator의 mediate() 구현은 완전히 다름
    • 공유할 코드가 없으므로 Abstract Class 불필요

Colleague는 필요에 따라

  • 공통 속성(mediator 참조)이 있으면 Abstract Class
  • 없으면 Interface

4. Mediator Pattern 핵심 구조

    Colleague (N)  ────────> Mediator (1)  ────────> Colleague (N)
   (통신 요청자들)        (통신 중재자)         (통신 수신자들)
   
   - 다대일대다 관계
   - Colleague는 서로를 모르고 Mediator만 앎
   - Mediator가 모든 통신을 중재
   - 통신 로직이 Mediator에 집중됨

항공 관제탑 비유:

  • 비행기(Colleague): 서로를 직접 알지 못함
  • 관제탑(Mediator): 모든 비행기의 착륙 요청을 조정
  • 비행기들은 관제탑에만 통신하면 됨

5. 예시 코드

Step 1: 인터페이스 및 추상 클래스 정의

// Mediator 인터페이스
public interface DialogDirector {
    void showDialog();
    void widgetChanged(Widget widget);
}

// Colleague 추상 클래스
public abstract class Widget {
    protected DialogDirector director;
    
    public Widget(DialogDirector director) {
        this.director = director;
    }
    
    // 변경 사항을 Director에게 알림
    public void changed() {
        director.widgetChanged(this);
    }
}

Step 2: Concrete Mediator 구현

public class FontDialogDirector implements DialogDirector {
    private ListBox fontList;
    private EntryField fontField;
    private Button okButton;
    
    @Override
    public void showDialog() {
        // 위젯들 생성
        fontList = new ListBox(this);
        fontField = new EntryField(this);
        okButton = new Button(this);
        
        // 초기 상태 설정
        okButton.setEnabled(false);
    }
    
    @Override
    public void widgetChanged(Widget widget) {
        // ListBox가 변경됨
        if (widget == fontList) {
            String selection = fontList.getSelection();
            fontField.setText(selection);
            okButton.setEnabled(true);
        }
        // EntryField가 변경됨
        else if (widget == fontField) {
            String text = fontField.getText();
            if (!text.isEmpty()) {
                fontList.selectItem(text);
                okButton.setEnabled(true);
            } else {
                okButton.setEnabled(false);
            }
        }
        // Button이 클릭됨
        else if (widget == okButton) {
            System.out.println("폰트 적용: " + fontField.getText());
        }
    }
}

Step 3: Concrete Colleague 구현

// ListBox 구현
public class ListBox extends Widget {
    private List<String> items = new ArrayList<>();
    private String selectedItem;
    
    public ListBox(DialogDirector director) {
        super(director);
    }
    
    public void addItem(String item) {
        items.add(item);
    }
    
    public String getSelection() {
        return selectedItem;
    }
    
    public void selectItem(String item) {
        this.selectedItem = item;
        changed();  // Mediator에게 알림!
    }
}

// EntryField 구현
public class EntryField extends Widget {
    private String text = "";
    
    public EntryField(DialogDirector director) {
        super(director);
    }
    
    public String getText() {
        return text;
    }
    
    public void setText(String text) {
        this.text = text;
        System.out.println("EntryField 업데이트: " + text);
    }
    
    public void userTyped(String text) {
        this.text = text;
        changed();  // Mediator에게 알림!
    }
}

// Button 구현
public class Button extends Widget {
    private boolean enabled = false;
    
    public Button(DialogDirector director) {
        super(director);
    }
    
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        System.out.println("Button " + (enabled ? "활성화" : "비활성화"));
    }
    
    public void click() {
        if (enabled) {
            changed();  // Mediator에게 알림!
        }
    }
}

Step 4: 실행

public class MediatorDemo {
    public static void main(String[] args) {
        // Mediator 생성
        FontDialogDirector director = new FontDialogDirector();
        director.showDialog();
        
        // 사용자가 ListBox에서 항목 선택
        System.out.println("\n=== ListBox에서 'Arial' 선택 ===");
        ListBox fontList = director.getFontList();
        fontList.addItem("Arial");
        fontList.addItem("Times New Roman");
        fontList.addItem("Courier");
        fontList.selectItem("Arial");
        
        // 사용자가 EntryField에 텍스트 입력
        System.out.println("\n=== EntryField에 'Courier' 입력 ===");
        EntryField fontField = director.getFontField();
        fontField.userTyped("Courier");
        
        // 사용자가 OK 버튼 클릭
        System.out.println("\n=== OK 버튼 클릭 ===");
        Button okButton = director.getOkButton();
        okButton.click();
    }
}

출력 결과

Button 비활성화

=== ListBox에서 'Arial' 선택 ===
EntryField 업데이트: Arial
Button 활성화

=== EntryField에 'Courier' 입력 ===
Button 활성화

=== OK 버튼 클릭 ===
폰트 적용: Courier

6. 간단한 채팅방 예시

// 채팅방 Mediator
public class ChatRoom {
    private Map<String, User> users = new HashMap<>();
    
    public void register(User user) {
        users.put(user.getName(), user);
    }
    
    public void sendMessage(String message, String fromUser) {
        // 모든 사용자에게 메시지 전송 (보낸 사람 제외)
        for (User user : users.values()) {
            if (!user.getName().equals(fromUser)) {
                user.receive(fromUser + ": " + message);
            }
        }
    }
}

// User Colleague
public class User {
    private String name;
    private ChatRoom chatRoom;
    
    public User(String name, ChatRoom chatRoom) {
        this.name = name;
        this.chatRoom = chatRoom;
        chatRoom.register(this);
    }
    
    public String getName() {
        return name;
    }
    
    public void send(String message) {
        System.out.println(name + " 전송: " + message);
        chatRoom.sendMessage(message, name);
    }
    
    public void receive(String message) {
        System.out.println(name + " 수신: " + message);
    }
}

// 실행
public class ChatDemo {
    public static void main(String[] args) {
        ChatRoom chatRoom = new ChatRoom();
        
        User alice = new User("Alice", chatRoom);
        User bob = new User("Bob", chatRoom);
        User charlie = new User("Charlie", chatRoom);
        
        alice.send("안녕하세요!");
        bob.send("반갑습니다!");
    }
}

출력:

Alice 전송: 안녕하세요!
Bob 수신: Alice: 안녕하세요!
Charlie 수신: Alice: 안녕하세요!
Bob 전송: 반갑습니다!
Alice 수신: Bob: 반갑습니다!
Charlie 수신: Bob: 반갑습니다!

7. Observer Pattern과의 비교

측면Mediator PatternObserver Pattern
통신 방식중앙 집중식 (Centralized)분산식 (Distributed)
관계다대일대다 (Colleagues ↔ Mediator)일대다 (Subject → Observers)
통신 흐름Mediator가 명시적으로 제어Subject가 자동으로 모든 Observer에게 알림
결합도Colleague는 Mediator만 앎Observer는 Subject를 알아야 함
재사용성Mediator는 재사용 어려움Observer/Subject는 재사용 쉬움
이해도통신 흐름 이해 쉬움통신 흐름 추적 어려움
사용 시기복잡한 객체 간 상호작용 조정데이터 변경 시 여러 객체 자동 업데이트

8. 핵심 정리

Mediator Pattern의 구성

요소역할특징
MediatorColleague 간 통신 중재통신 로직이 집중됨
ColleagueMediator를 통해 통신서로를 직접 알지 못함
Concrete Mediator실제 중재 로직 구현특정 상황에 맞춤
Concrete Colleague실제 기능 수행Mediator를 통해서만 통신

언제 사용하는가?

  • 객체 간 통신이 복잡하고 잘 정의되어 있을 때
  • 객체 간 관계가 너무 많아 통제가 어려울 때
  • 객체 간 의존성을 줄이고 싶을 때
  • GUI 컴포넌트 간 상호작용을 관리할 때 (가장 흔한 사용 사례)

장단점

장점 (+)

  • 객체 간 결합도 감소 (느슨한 결합)
  • 통신 로직이 한 곳에 집중되어 이해하기 쉬움
  • 객체들을 독립적으로 재사용 가능
  • 통신 로직 변경 시 Mediator만 수정

단점 (-)

  • Mediator가 복잡해질 수 있음 (God Object)
  • Mediator가 특정 상황에 맞춰져 재사용 어려움

핵심 원칙

  • 중재자 중심: 모든 통신이 Mediator를 거침
  • 느슨한 결합: Colleague는 서로를 모름
  • 중앙 집중식 제어: 통신 로직이 Mediator에 집중
  • 명시적 통신: Mediator가 누구와 누구를 연결할지 결정
profile
다들 응원합니다.

0개의 댓글