본문 바로가기
면접을 위한 CS 전공지식 노트/1장 디자인 패턴과 프로그래밍 패러다임

옵저버패턴

by alswlfl 2024. 9. 6.

옵저버 패턴은 옵저버(=관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 혹은 간접적으로 각 관찰자들에게 변화를 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴 ⇒ Publisher/Subscriber(발행/구독) 모델

  • 1:M 의존성을 갖는다.
  • 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다.

ex) 유튜브 구독

  • 유튜버 = 발행자 = 대상자
  • 구독자 = 관찰자

유튜버가 영상을 올리거나 글이 올라오면 구독자들에게 영상이 업로드 되거나 글이 올라왔다는 알림이 온다. 유튜브 채널을 구독하지 않은 사람들에게는 알림이 가지 않는다.

옵저버 패턴 흐름

  1. Subject(관찰 대상자)와 여러개의 Observer(관찰자)가 일 대 다 관계로 구성되어 있음
  2. 관찰 대상인 Subject 상태가 변경되면 Observer에게 변경 사항을 통보한다. ⇒ notifyObserver()
  3. 통보를 받은 Observer는 값을 변경하거나 삭제하거나 등 동작을 수행한다 ⇒ update()
  4. Observer들은 Observer 리스트에서 추가 혹은 삭제될 수 있다.
// 관찰 대상자 = 발행자
interface ISubject {
  registerObserver(o: IObserver): void;
  removeObserver(o: IObserver): void;
  notifyObserver(): void;
}

class Subject implements ISubject {
  // 관찰자들을 등록해서 담는 리스트
  observers: IObserver[] = [];

  // 관찰자 리스트에 등록
  registerObserver(o: IObserver) {
    this.observers.push(o);
    console.log(o + ' 구독 완료');
  }

  // 관찰자 리스트에서 삭제
  removeObserver(o: IObserver): void {
    this.observers = this.observers.filter((ob) => ob !== o);
    console.log(o + ' 구독 취소');
  }

  // 관찰자에게 이벤트 송신
  notifyObserver(): void {
    for (let observer of this.observers) {
      observer.update();
    }
  }
}

// 관찰자 = 구독자
interface IObserver {
  update(): void;
}

class ObserverA implements IObserver {
  update() {
    console.log('ObserverA님! 알림이 도착하였습니다.');
  }
  toString() {
    return 'ObserverA';
  }
}
class ObserverB implements IObserver {
  update() {
    console.log('ObserverB님! 알림이 도착하였습니다.');
  }
  toString() {
    return 'ObserverB';
  }
}

// 발행자 등록
const publisher = new Subject();

// 구독자 리스트로 등록
const o1 = new ObserverA();
const o2 = new ObserverB();
publisher.registerObserver(o1); // ObserverA 구독 완료
publisher.registerObserver(o2); // ObserverB 구독 완료

// 이벤트 전파
publisher.notifyObserver();

// ObserverA만 구독 취소
publisher.removeObserver(o1); // ObserverA 구독 취소

// ObserverB에게만 이벤트 전파
publisher.notifyObserver(); // ObserverB님! 알림이 도착하였습니다.

사용 상황

  • 대상 객체의 상태가 변경될 때마다 다른 객체의 동작을 트리거해야 할 때
  • 한 객체의 상태가 변경되면 다른 객체도 변경해야 할 때

장점

  • ObserverSubject의 상태 변화를 주기적으로 조회하지 않아도 자동으로 감지 가능
  • Subject 코드 변경하지 않고도 새로운 Observer 추가 가능 ⇒ OCP 준수
  • 런타임 시점에서 발행자와 구독 알림 관계 맺을 수 있음
  • 상태 변경하는 객체와 변경 감지하는 객체 간 관계를 느슨하게 유지 가능
    • Subject는 Observer가 누구인지 알 필요 없음

단점

  • Observer는 알림 순서 제어할 수 없고 무작위 순서로 알림을 받는다.
  • 코드 복잡도 중가
  • 다수의 Observer 객체를 등록한 이후 해지하지 않으면 메모리 누수 발생

유튜브 구독 예제

// 유튜버
interface ISubject {
  registerObserver(o: IObserver): void;
  removeObserver(o: IObserver): void;
  notifyObserver(): void;
}

class YoutuberSubject implements ISubject {
  private _youtubeName: string;
  constructor(name: string) {
    this._youtubeName = name;
  }

  // 구독자들 관리하는 리스트
  observers: IObserver[] = [];

  // 동영상 리스트
  videoList: string[] = [];

  // 동영상 업로드
  uploadVideo(link: string) {
    this.videoList.push(link);

    this.notifyObserver();
  }

  // 구독자 추가
  registerObserver(o: IObserver) {
    this.observers.push(o);
    console.log(o + ' 구독 완료');
  }

  // 구독자 취소
  removeObserver(o: IObserver): void {
    this.observers = this.observers.filter((ob) => ob !== o);
    console.log(o + ' 구독 취소');
  }

  // 구독자 알림 전송(업로드 시)
  notifyObserver(): void {
    for (let observer of this.observers) {
      observer.alarm(this._youtubeName, observer._username);
    }
  }
}

// 구독자
interface IObserver {
  _username: string;
  alarm(youtubeName: string, name: string): void;
}

class Subscriber implements IObserver {
  _username: string;
  constructor(name: string) {
    this._username = name;
  }
  alarm(youtubeName: string, name: string): void {
    console.log(`${name}님! ${youtubeName}의 영상이 업로드되었습니다.`);
  }
  toString() {
    return this._username;
  }
}

// 유튜브 채널 등록
const publisher = new YoutuberSubject('observerPattern');

// 구독자 등록
const s1 = new Subscriber('SubscriberA');
const s2 = new Subscriber('SubscriberB');
const s3 = new Subscriber('SubscriberC');
publisher.registerObserver(s1);
publisher.registerObserver(s2);
publisher.registerObserver(s3);

// 영상 업로드
publisher.uploadVideo('first_video');

// SubscriberA 구독 취소
publisher.removeObserver(s1);

// 영상 업로드 -> A 제외하고 알림 감
publisher.uploadVideo('second_video');

[참고 사이트]

💠 옵저버(Observer) 패턴 - 완벽 마스터하기

'면접을 위한 CS 전공지식 노트 > 1장 디자인 패턴과 프로그래밍 패러다임' 카테고리의 다른 글

MVP 패턴  (0) 2024.09.10
MVC 패턴  (1) 2024.09.06
MVVM 패턴  (0) 2024.09.05
전략 패턴  (0) 2024.09.05