dh_0e

[Design Pattern] Behavior Design Patterns IV (Mediator Pattern, Memento Pattern, Observer Pattern) 본문

Software Analysis & Design/Design Patterns

[Design Pattern] Behavior Design Patterns IV (Mediator Pattern, Memento Pattern, Observer Pattern)

dh_0e 2025. 11. 27. 19:14

중재자 패턴(Mediator Pattern)

  • 정의: 객체 간의 직접 통신을 제한하고 중재자 객체(mediator)를 통해서만 협력하도록 하는 행동 패턴
  • 중재자(Mediator): 객체 간의 통신을 관리하고 매개체 역할을 수행하여 객체 간의 결합을 낮추고 유연성을 확보

  • 필요 상황
    • 여러 객체들이 상호작용하는 복잡한 상황에서 각 객체가 서로를 참조하게 되어 한 클래스 수정 시 연관된 클래스들의 수정이 연쇄적으로 발생할 때 필요
    • ex) 프로필 편집 대화 상자

Before

  • 아이디어
    • 각 객체 간의 연결을 느슨하게 만들어야 함
      • 객체 간의 직접적인 통신은 중단하고, 이러한 호출을 대신 처리할 수 있는 중재제 객체를 두어서 간접적으로 협력시킴
      • M:N의 관계를 M:1의 관계로 전환

After

 

Mediator Pattern 구조

구성 요소 역할 상세 설명
1. Components 중재자 참조를 통해 소통 컴포넌트들은 서로를 인식하지 않아야 하며, 일이 발생하면 중재자에게만 알려야 합니다.
2. Mediator notify 메서드를 통해 중재 컴포넌트들을 다른 중재자에 연결하여 재사용할 수 있습니다.
3. Concrete Mediator 컴포넌트 간의 관계 캡슐화 중재자가 관리하는 컴포넌트의 참조를 유지하며, 다양한 컴포넌트 간의 관계를 캡슐화합니다.

Mediator Pattern 예시 코드(Mediator, ConcreteMediator)

  • sendMessage가 핵심

Mediator Pattern 코드 예시(Components)

  • 장점
    • 다양한 컴포넌트 간의 통신을 한 곳으로 추출하여 코드 이해 및 유지관리가 쉬워짐
    • 프로그램 컴포넌트 간의 결합도를 줄일 수 있음
    • 개별 컴포넌트들을 더 쉽게 재사용할 수 있음
    • OCP 준수: 실제 컴포넌트들을 변경하지 않아도 새로운 중재자를 도입할 수 있음
  • 단점
    • 중재자가 너무 많은 책임을 지게 되어 GodObject로 발전할 수 있음 & SRP 위반

 

퍼시드 패턴과의 차이점

  • 퍼사드 패턴: 라이브러리(or 서브시스템)에 대해 사용하기 편하기 간편한 인터페이스를 구성하기 위한 구조  패턴
특징 Mediator Pattern (중재자) Facade Pattern (퍼사드)
목적 시스템 컴포넌트 간의 통신을 중앙 집중화 하위 시스템에 대한 단순화된 인터페이스 정의
객체 인지 컴포넌트들은 중재자를 인식하고 통신 하위 시스템 자체는 퍼사드를 인식하지 못함
내부 통신 중재자를 통해서만 간접 통신 유도 하위 시스템 내 객체들은 서로 직접 통신 가능
하위 시스템 통신 하위 시스템 내 객체들의 통신에 관여 하위 시스템 내 객체들의 통신에 관여할 수 없음

 


 

메멘토 패턴(Memento Pattern)

  • 정의: 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태값을 저장하고 복원할 수 있게 해주는 행동 패턴
    (= 스냅샷)

  • 필요 상황
    • 텍스트 편집기 등에서 실행 취소(Undo) 기능을 구현할 때, 특정 작업 수행 전에 객체의 상태를 기록하고, 실행 취소 시 최신 스냅샷을 가져와 복원해야 함

  • 문제점
    • 이를 위해서 객체의 모든 필드를 살펴본 후 해당 값들을 복사해야 함
    • 그러나 대부분의 실제 객체들은 모든 중요한 데이터를 비공개함 (캡슐화)
    • 공개된 필드라고 할지라도, 객체 일부가 수정되면 복사를 맡은 클래스들 역시 변경이 되어야 함

  • 아이디어
    • 상태 스냅샷들의 생성을 해당 상태의 실제 소유자인 originator 객체에 위임
    • memento라는 특수 객체에 복사본을 저장
      • 메멘토의 내용에는 메멘토를 생성한 객체를 제외한 다른 어떤 객체도 접근 불가

스냅샷 예시
Memento Pattern 구조

 

구성 요소 역할 특징
1. Originator 자신의 상태에 대한 스냅샷 생성 및 복원 private 필드에 직접 접근할 수 있도록 메멘토 클래스를 내부에 중첩하여 설계할 수 있습니다.
2. Memento 스냅샷 역할 관행적으로 불변(immutable)으로 만들고 생성자를 통해 값을 저장합니다.
3. Caretaker 메멘토들의 스택 저장 오리지네이터의 기록을 추적하며, 복원 시 사용할 메멘토 객체들을 관리합니다.

Memento Pattern 코드 예시(Originator, Memento)ww

  • 중첩 클래스바깥 클래스의 private 멤버에 접근할 수 있음(Java/C#)
    • Snapshot에서 Editor의 private 멤버에 접근 가능
  • 메멘토를 내부 중첩으로 설계하는 이유는 기능 때문이라기보다 캡슐화를 지키기 위한 설계 트릭
    • 외부엔 메멘토를 불투명한 토큰처럼 보이게 하고, Originator만 복원할 수 있게 만드는 것

Memento Pattern 코드 예시(Caretaker)

  •  undo에서 사용하는 restore: 이전 상태를 param으로 받아 현 상태를 바꿔줌
  • this.stack = new Stack<>(); - 자료형 선언할 때 적었으니 생략 가능

Memento Pattern 코드 예시 (Client)

// 출력 결과
design pattern 1 - Memento
design pattern 2 - Iterator
design pattern 3 - Prototype

=== 복구 ===

design pattern 3 - Prototype
design pattern 2 - Iterator
design pattern 1 - Memento
  • 장점
    • 캡슐화를 위반하지 않고 객체의 상태 스냅샷을 생성 가능
    • 케어테이커가 상태 기록을 유지(Stack)하도록 하여 오리지네이터의 코드를 단순화할 수 있음
      • 저장본들 관리할 필요 없이 상태 저장/복원 기능만 구현하면 됨
      • 사실 정석으론 내용 확인도 못함 오리지네이터만 내용 확인 가능, 케어테이커는 껍데기만 들고 있는거
        • 정석: Snapshot을 가짜 자료형인 Memento로 구현받 사용 (업캐스팅: Upcasting)
// 정석 코드 (command가 Snapshot 확인 못하고 들고만 있다가 Editor(Originator)에게 전달)
public interface Memento {
    // 텅 비어있음 (내용물을 숨기기 위한 마커)
}

public class Editor {
    // ★ private으로 선언하면 Command는 이 클래스의 존재조차 모름 (Type도 못 씀)
    private static class Snapshot implements Memento { ... }

    public Memento create() {
        return new Snapshot(...); // 밖에는 껍데기(인터페이스)만 던져줌
    }
}

public class Command {
    // [핵심 4] Snapshot 타입을 못 쓰니까 Memento(껍데기)로 스택을 만듦
    private Stack<Memento> history = new Stack<>();

    public void makeBackup(Editor editor) {
        // 받아올 때도 Memento 타입으로 받음
        Memento m = editor.create();
        history.push(m);
    }

    public void undo(Editor editor) {
        if (history.isEmpty()) return;

        // 꺼낼 때도 Memento 타입
        Memento m = history.pop();
        
        // 내용물은 못 보지만, 주인(Editor)한테 그대로 돌려줌
        editor.restore(m);
    }
}
  • 단점
    • 클라이언트가 메멘토를 너무 자주 생성하면 메모리 사용이 증가
      • 케어테이커에게 오래된 메멘토를 삭제하고 관리하는 역할이 추가될 수 있음

 


 

옵저버 패턴(Observer Pattern)

  • 정의: 옵저버들이 관찰하고 있는 대상(Publisher/Subject) 상태 변화가 있을 때마다 대상자는 각 관찰자(Observer/Subscriber)에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴(= 발행-구독 모델)
  • ex) 유튜버 구독해 놓고 알림 켜놨다고 생각하면 됨

Observer Pattern 구조

구성 요소 역할 상세 설명
1. Publisher (관찰대상자) 구독자 관리 및 이벤트 발행 상태가 바뀌면 변경 사항을 관찰자에게 통보합니다. 인터페이스로 분리할 수도 있습니다.
3. Subscriber (관찰자) 알림 인터페이스 선언 및 업데이트 통보를 받은 관찰자는 필요에 맞추어 적절하게 업데이트를 수행합니다.
2. 알림 (Notification) 이벤트 발생 시 전송 Publisher는 새 이벤트 발생 시 각 구독자 객체에게 알림을 보냅니다. 구독 추가/취소는 언제든지 가능합니다.
  • 옵저버 패턴의 흐름
    1. 한 개의 관찰 대상자와 여러 개의 관찰자로 1:N 관계 구성
    2. 관찰 대상자의 상태가 바뀌면 변경사항을 관찰자에게 통보
    3. 통보를 받은 관찰자는 적절하게 필요에 맞추어 업데이트
    4. 언제든지 구독 추가/취소 가능

Observer Pattern 코드 예시(Subscriber, Concrete Subscribers)

 

Observer Pattern 코드 예시 (ConcretePublisher, PublisherInterface, Client)

  • 구조 다이어그램엔 없지만 Publisher가 Interface, WeatherAPI가 Concrete publisher라고 생각하면 됨
  • 장점
    • 관찰 대상자(Publisher)의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지
    • Publisher의 코드를 변경하지 않고도 새 구독자(Subscriber) 클래스를 쉽게 도입할 수 있음
    • 런타임 시점에 발행자와 구독 알림 관계를 맺음
    • 상태 변경하는 객체(Publisher)변경을 감지하는 객체(Subscriber)의 관계를 느슨하게 유지
  • 단점
    • 구독자는 알림 순서를 제어할 수 없고 무작위로 통보만 받음
    • 사용하지 않는 Observer 객체를 해지하지 않으면 메모리 낭비가 발생할 수 있음

 


 

Summary

  • 중재자 패턴(Mediator Pattern)
    • 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 행동 패턴
  • 메멘토 패턴(Memento Pattern =Snapshot)
    • 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태 값을 저장하고 복원할 수 있게 해주는 행동 패턴
  • 옵저버 패턴(Observer Pattern)
    • 옵저버(Subscriber)들이 관찰하고 있는 대상(Publisher)의 상태 변화가 있을 때마다 대상자(Publisher)는 직접 각 관찰자(Subscriber)에게 통지하고 관찰자들은 알림을 받아 조치를 취하는 행동 패턴