본문 바로가기
소프트웨어/디자인 패턴

[2] Observer Pattern

by Riverandeye 2020. 10. 22.

옵저버 패턴이란 "자신의 상태"가 변했을 때 다른 Observer (Listener) 들에게 notify 할 수 있는 객체(주제)를 정의합니다.

한 객체의 상태가 바뀌면, 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 "일대다" 의존성을 정의합니다. 

 

제가 처음에 이 옵저버 패턴을 공부했을 때 어려웠던 점이

옵저버 라는 이름에 너무 집착해서

어떤 대상이 다른 대상을 Observe, 즉 관찰하고 있어야 하는 것 아닌가? 라는 생각에

구독 주제가 되는 대상이 실제로 다른 대상들에게 알리는 메소드를 가지고 있는 것이 잘 이해가 되지 않았습니다. 

 

각 객체간 소통을 할 때 객체에 정의되어 있는 메소드를 이용하는데, 

옵저버 패턴에서는 "알리는 객체" 가 "구독하는 객체"의 메소드 하나만 알면 되고

내부 구현에 대해서는 몰라도 됩니다. (혹은 그렇게 구성되어야 합니다)

 

구조

UML Diagram (출처 : head first design pattern)

 

Observer Pattern UML and Example Sequene Diagram (출처 : https://en.wikipedia.org/wiki/Observer_pattern)

위 이미지의 왼쪽은 옵저버 패턴의 UML 다이어그램이고

오른쪽은 Sequence Diagram 입니다. 

 

왼쪽 이미지를 먼저 보면

Subject 라는 클래스가 Observer에 화살표를 향하고 있는 것을 보면 Subject는 Observer에 대해 알아야 합니다. 

Observer와 Subject는 인터페이스 혹은 추상클래스로, 실제 구현된 하위 클래스들이 존재합니다. 

 

오른쪽 이미지를 보면

처음 Subject의 attach함수에 Observer를 인자로 넘겨 Subject를 구독합니다. (Subject가 Observer를 알게 합니다) 

그 후 Subject의 State가 바뀔 때 Subject는 자신이 알고 있는 Observer들에게 해당 내용을 전달합니다.

이 때 Subject는 Observer의 인터페이스에 정의된 update 메소드를 사용하여 알리게 됩니다. 

 

메시지를 전달받은 Observer들은 Subject의 상태를 가져오고, 개별 비즈니스 로직을 수행하게 됩니다. 

 

이점

해당 정보를 필요로 하는 대상(Observer)이, 이전 정보를 가지고 있지 않아도 업데이트 되었다는 사실을 알 수 있습니다. 

정보를 담는 대상(Subject)는, 필요로 하는 대상(Observer)이

어떤 방식으로 정보를 이용하는지(구현)에 대해 몰라도 됩니다. 

 

구현

Observer

import Subject from "./subject";

export default interface Observer {
  update: (subject: Subject) => void;
}

 

정보를 업데이트 받고 싶어하는 객체 (관찰자)에 대한 인터페이스입니다. 

Subject 로부터 정보를 업데이트 받을 때, 어떻게 받을것인지에 대해서 명시해주는 update 함수를 가지고 있습니다.

모든 Observer로 하여금 update 메소드를 갖고 있게 함으로써, 동일한 소통 창구를 마련하는 것입니다. 

 

 

Subject

import Observer from "./observer";

export default class Subject {
  private observers: Observer[];

  public constructor() {
    this.observers = [];
  }

  public registerObserver(observer: Observer) {
    this.observers.push(observer);
  }

  public removeObserver(observer: Observer) {
    this.observers = this.observers.filter((v) => v !== observer);
  }

  protected notifyObserver(subject: Subject) {
    this.observers.forEach((observer) => observer.update(subject));
  }
}

정보를 담고, 변경사항을 관찰자들에게 알려주는 객체입니다.

정보를 전달하기 위해, 정보를 필요로 하는 관찰자들의 목록을 가지고 있습니다.

그리고 관찰자들의 목록을 업데이트하는 registerObserver, removeObserver 메소드를 가지고 있습니다.

마지막으로, 정보가 변경되었을 때 각 관찰자들에게 그 사실을 전달할 수 있는 notifyObserver 메소드를 가지고 있습니다.

 

News

import Subject from "./lib/subject";

export default class NewsApp extends Subject {
  public constructor(public name: string, public message: string) {
    super();
  }

  updateNews(name: string, message: string) {
    this.message = message;
    this.name = name;

    this.notifyObserver(this);
  }
}

Subject의 구현체입니다.

updateNews 메소드 내에서 notifyObserver 메소드를 사용함으로써

변경사항이 발생하는 경우 그 사실을 Observer들에게 전달하는 것을 확인할 수 있습니다. 

 

Reader

import Observer from "./lib/observer";
import Subject from "./lib/subject";

import NewsApp from "./news";

export default class Reader implements Observer {
  public constructor(public name: string) {}

  public update(subject: Subject) {
    if (subject instanceof NewsApp) {
      console.log(
        `${this.name} : 흠 ${subject.name} 뉴스의 새로 업데이트된 내용 ${subject.message} 는 정말 흥미롭구만`,
      );
    }
  }
}

뉴스를 구독하는 독자 클래스입니다. 

어떠한 업데이트를 받았을 때, 그게 뉴스면 그 뉴스에 대해서 이야기하는 것을 볼 수 있습니다. 

 

Main 함수

import NewsApp from "./news";
import Reader from "./reader";

const main = () => {
  const newsApp = new NewsApp("동아", "물가가 떨어졌다.");
  const 관훈 = new Reader("관훈");
  const 진용 = new Reader("진용");

  newsApp.registerObserver(관훈);
  newsApp.registerObserver(진용);
  newsApp.updateNews("경향", "마스크가 동났다");

  newsApp.removeObserver(관훈);
  newsApp.updateNews("유투브", "미노이 Vlog");
};

main();

위 코드를 실행하면 다음과 같은 결과가 나타납니다. 

 

결과

중간에 removeObserver를 통해 Observer를 제거했더니 나타나지 않는 것을 볼 수 있습니다.

 

이 패턴을 이용하면서 지킨 디자인 원칙은 다음과 같습니다.

 

서로 상호작용하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다

 

'소프트웨어 > 디자인 패턴' 카테고리의 다른 글

[6] 싱글턴 패턴  (0) 2020.11.05
[5] 팩토리 메소드 패턴  (0) 2020.10.29
[4] Decorator Pattern  (0) 2020.10.28
[3] Strategy Pattern  (2) 2020.10.25
[1] Reactor Pattern  (4) 2020.10.19

댓글