코린이의 개발 일지

Observer 패턴 살펴보기 본문

CS공부/디자인패턴

Observer 패턴 살펴보기

폴라민 2023. 2. 6. 18:09
반응형

Observer pattern

  • 객체의 상태 변화를 관찰하는 옵저버들의 목록을 객체에 등록하여, 상태 변화가 있을 때마다 notify를 통해 객체가 직접 목록의 각 옵저버에게 통지하도록하는 디자인 패턴

 

 

구현 예시

  • 기본적인 인터페이스는 아래와 같다.

Subject

class Subject {
  #state;
  constructor() {
    this._observers = new Set();
  }
  subscribe(observer) {
    this._observers.add(observer);
  }
  unsubscribe(observer) {
    this._observers.delete(observer);
  }
  notify() {
    this._observers.forEach((observer) => observer.update(this.#state));
  }
}

Observer

class Observer {
  update(newState) {}
}

 

observer 패턴이 동작하는 방식을 간단히 살펴보면

  • Observer들은 notify가 발생했을 때, 할 행동을 정의한다 (update)
  • Subject는 자신을 구독하는 구독자들(Observer들)을 관리하는 메소드를 정의한다.
    • 구독
    • 구독 취소
    • 이벤트 발생
  • 이때 핵심은 Subject에서 notify할때, Observer의 메소드를 직접 호출한다는 것이다.
    • 이부분이 Pubsub 패턴과의 차이점인데, Observer 패턴은 Observer와 Subject가 서로 인지하고 있고, Subject가 Observer에 직접 알려준다. (따라서 pubsub 패턴보다 Observer 패턴이 더 결합도가 높다)

직접 구현하고 실행해보자

위의 클래스를 상속받아와서 구현한다.

 

 

예시 Observer

class ObserverA extends Observer {
  state;
  update(newState) {
    this.state = newState;
    console.log("A update to", this.state);
  }
  display() {
    console.log("Observer A:", this.state);
  }
}

class ObserverB extends Observer {
  state;
  update(newState) {
    this.state = newState * 2;
    console.log("B update to", this.state);
  }
  display() {
    console.log("Observer B:", this.state);
  }
}

 

 

예시 Subjects

class UpdateSubject extends Subject {
  setState(newState) {
    this.state = newState;
    this.notify();
  }
  getState() {
    return this.state;
  }
}

 

 

사용 코드

const obsA = new ObserverA();
const obsB = new ObserverB();

const update = new UpdateSubject();

update.subscribe(obsA);
update.subscribe(obsB);

update.setState(3);

/*
출력 결과

A update to 3
B update to 6
*/

 

 

그렇다면 만약, update 이벤트 발행이 아닌, display 이벤트를 발행하고 싶다면 어떻게 해야할까?

아래와 같은 클래스를 하나 더 정의 해주어야한다.

 

 

class DisplaySubject extends Subject {
  notify() {
    this._observers.forEach((observer) => observer.display());
  }
  getState() {
    return this.state;
  }
}

 

 

사용코드

const obsA = new ObserverA();
const obsB = new ObserverB();

const display = new DisplaySubject();
const update = new UpdateSubject();

update.subscribe(obsA);
update.subscribe(obsB);

display.subscribe(obsA);
update.setState(3);
display.notify();

/*
출력 결과

A update to 3
B update to 6
Observer A: 3
*/

 

PubSub패턴으로 바꿔보기

 

위와 같은 동작을 PubSub으로 바꾸어보자.

 

 

PubSub

class PubSub {
  constructor() {
    this.events = new Map();
  }

  subscribe(eventType, func) {
    if (!this.events.has(eventType)) {
      this.events.set(eventType, []);
    }

    const funcs = this.events.get(eventType);
    funcs.push(func);
    this.events.set(eventType, funcs);
  }

  publish(eventType, ...args) {
    const funcs = this.events.get(eventType);
    funcs.forEach((func) => func.apply(null, args));
  }
}

 

 

Publisher

class Publisher {
  constructor(topic) {
    this.topic = topic;
  }
}

class UpdatePublisher extends Publisher {
  publish(newState) {
    ContentServer.publish(this.topic, newState);
  }
}

class DisplayPublisher extends Publisher {
  publish() {
    ContentServer.publish(this.topic);
  }
}

 

 

Subscriber

class Subscriber {
    subscribe(topic) {}
  update(newState) {}
}

class SubscriberA extends Subscriber { 
  state;
  subscribe(topic) {
    switch (topic) {
      case "update":
        ContentServer.subscribe(topic, this.update.bind(this));
        break;
      default:
        ContentServer.subscribe(topic, this.display.bind(this));
    }
  }
  update(newState) {
    this.state = newState;
    console.log("A update to", this.state);
  }
  display() {
    console.log("Subscriber A:", this.state);
  }
}

class SubscriberB extends Subscriber {
  state;
  subscribe(topic) {
    switch (topic) {
      case "update":
        ContentServer.subscribe(topic, this.update.bind(this));
        break;
      default:
        ContentServer.subscribe(topic, this.display.bind(this));
    }
  }
  update(newState) {
    this.state = newState * 2;
    console.log("B update to", this.state);
  }
  display() {
    console.log("Subscriber B:", this.state);
  }
}

 

 

사용 코드

const ContentServer = new PubSub();

const subA = new SubscriberA();
const subB = new SubscriberB();
const display = new DisplayPublisher("display");
const update = new UpdatePublisher("update");

subA.subscribe("update");
subA.subscribe("display");
subB.subscribe("display");

update.publish(4);
display.publish();

/*
출력 결과

A update to 4
Subscriber A: 4
Subscriber B: undefined
*/
  • Observer 패턴에서와 다르게, Publisher와 Subscriber가 전혀 접점이 없다.
  • PubSub이라는 일종의 broker를 통해 Publisher와 Subscriber가 연결 되어 있기 때문에, Publisher가 Subscriber의 위치나 존재를 알 필요없이 Message Queue와 같은 Broker역활을 하는 중간지점에 메시지를 던져 놓기만 하면 된다.
  • 반대로 Subscriber 역시 Publisher의 위치나 존재를 알 필요없이 Broker에 할당된 작업만 모니터링하다 할당 받아 작업하면 되기 때문에 Publisher와 Subscriber가 서로 알 필요가 없다.

 

최종 정리

  • Observer 패턴이란 객체의 상태 변화를 관찰하는 옵저버들의 목록을 객체에 등록하여, 상태 변화가 있을 때마다 notify를 통해 객체가 직접 목록의 각 옵저버에게 통지하도록하는 디자인 패턴이다.
  • Observer 패턴과 Pub-Sub 패턴의 차이점
    • 가장 큰 차이점은 중간에 Message Broker혹은 Event Bus가 존재하는지 여부이다.
    • Observer 패턴의 경우 Subject에 Observer를 등록하고 Subject가 notify를 통해 직접 Observer에 알려주어야 한다.
    • Observer 패턴이 더 결합도가 높다.
반응형
Comments