포스트

[Behavioral Pattern] 옵저버 패턴 (Observer Pattern)

Design Pattern / Behavioral Pattern

옵저버 패턴의 정의와 해당 디자인 패턴의 예제 코드를 통한 이해 및 설명 정리

개념

  • 어떤 이벤트가 일어나는 것을 감시하는 패턴을 의미

  • 함수로 직접 요청한 적 없지만 시스템에 의해 발생하는 동작들을 이벤트라고 하는데, 이러한 이벤트들을 감시하여 이벤트가 발생할 때마다 미리 정의해 둔 어떠한 동작을 즉각 수행하게 해주는 프로그래밍 패턴

  • 옵저버 패턴을 활용하면 다른 객체의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있기 때문에, 이벤트에 대한 처리를 자주 해야 하는 프로그램이라면 매우 효율적인 프로그램을 작성할 수 있음

  • 여타 다른 디자인 패턴들과 다르게 일대다(one-to-many) 의존성을 가지는데, 주로 분산 이벤틈 핸들링 시스템 구현을 하는데 사용함

    • Pub/Sub 모델로도 알려져 있음

패턴 구조

observer

  • Subject

    • 관찰 대상자를 정의하는 인터페이스
  • ConcreteSubject

    • 관찰 당하는 대상자 / 발행자 / 게시자

    • Observer들을 리스트(List, Map, Set 등…)로 모아 합성(composition)하여 가지고 있음

    • Subject의 역할은 관찰자인 Observer들을 내부 리스트에 등록/삭제하는 인프라를 갖고 있음 (register, remove)

    • Subject가 상태를 변경하거나 어떤 동작을 실행할 때, Observer들에게 이벤트 알림(notify)을 발행

  • Observer

    • 구독자들을 묶는 인터페이스 (다형성)
  • ConcreteObserver

    • 관찰자 / 구독자 / 알림 수신자

    • Observer들은 Subject가 발행한 알림에 대해 현재 상태를 취득

    • Subject의 업데이트에 대해 전후 정보를 처리

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
interface ISubject {
  registerObserver(o: Observer): void; // 구독 추가
  removeObserver(o: Observer): void; // 구독 삭제
  notifyObservers(): void; // Subject 객체의 상태 변경 시, 이를 모든 옵저버에게 알림
}

class WeatherAPI implements ISubject {
  temp!: number; // 기온
  humidity!: number; // 습도
  pressure!: number; // 기압

  // 구독자들을 담아 관리하는 리스트
  subscribers: Array<Observer> = new Array();

  measurementsChanged(): void {
    // 랜덤 값을 통해 날씨 API처럼 구현
    this.temp = parseFloat((Math.random() * 100).toFixed(2));
    this.humidity = parseFloat((Math.random() * 100).toFixed(2));
    this.pressure = parseFloat((Math.random() * 100).toFixed(2));

    this.notifyObservers(); // 값이 변화하면 바로 옵저버들에게 발행
  }

  public registerObserver(o: Observer): void {
    this.subscribers.push(o); // 구독자 추가
  }

  public removeObserver(o: Observer): void {
    const idx = this.subscribers.indexOf(o);
    if (idx > -1) this.subscribers.splice(idx, 1); // 구독자 삭제
  }

  // 이벤트 전파
  public notifyObservers(): void {
    // JAVA -> for(Observer o: subscribers) {
    for (let o of this.subscribers) {
      o.display(this); // 자신의 객체를 매개변수로 줘서 현재 자신의 상태를 구독자에게 알림
    }
  }
}

interface Observer {
  display(api: WeatherAPI): void;
}

class KoreanUser implements Observer {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  public display(api: WeatherAPI): void {
    console.log(
      `${this.name}님이 현재 날씨 상태를 조회함 : ${api.temp}°C ${api.humidity}g/m3 ${api.pressure}hPa`
    );
  }
}

class Client {
  public static main(_args?: string[]): void {
    const api: WeatherAPI = new WeatherAPI();
    const user_1 = new KoreanUser("한형진");
    const user_2 = new KoreanUser("홍길동");
    const user_3 = new KoreanUser("임꺽정");
    const user_4 = new KoreanUser("한진형");

    api.registerObserver(user_1);
    api.registerObserver(user_2);
    api.registerObserver(user_3);

    api.measurementsChanged();

    api.removeObserver(user_3);
    api.registerObserver(user_4);

    console.log("");

    api.measurementsChanged();
  }
}

Client.main();
// 한형진님이 현재 날씨 상태를 조회함 : 2.55°C 12.32g/m3 92.05hPa
// 홍길동님이 현재 날씨 상태를 조회함 : 2.55°C 12.32g/m3 92.05hPa
// 임꺽정님이 현재 날씨 상태를 조회함 : 2.55°C 12.32g/m3 92.05hPa

// 한형진님이 현재 날씨 상태를 조회함 : 47.03°C 65.19g/m3 43.94hPa
// 홍길동님이 현재 날씨 상태를 조회함 : 47.03°C 65.19g/m3 43.94hPa
// 한진형님이 현재 날씨 상태를 조회함 : 47.03°C 65.19g/m3 43.94hPa

참고한 출처 사이트

Refactoring GURU

Inpa Dev Blog (디자인 패턴)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.