Observer vs PUB-SUB

김병엽·2024년 9월 10일

observer 패턴과 pub-sub 패턴에 대해 알아보자.


Observer


구성 요소

  • Subject (관찰 대상): 상태 변화를 감지하고, 그 변화를 Observer들에게 알리는 역할을 한다. 이벤트가 발생하면 직접적으로 Observer들에게 알림을 전달한다.
  • Observer (관찰자): Subject의 상태 변화를 감시하고, Subject에서 발생한 변화를 알림받아 그에 맞춰 동작을 수행한다.

동작 방식

  • 직접적인 의존 관계: Observer 패턴에서는 Observer가 직접 Subject에 등록된다. Subject는 자신의 상태가 변경될 때, 등록된 모든 Observer에게 직접적으로 알림을 보낸다.
  • 알림 전달: Subject가 상태를 변경하면, 모든 등록된 Observer들에게 직접 상태 변경을 알린다. Observer는 이 알림을 받아서 동작을 처리한다.

특징

  • 강한 결합: Subject는 Observer들이 누구인지 알고 있으며, 직접적으로 통지한다.
  • 동기적 처리: 일반적으로 상태 변화가 발생하면 즉시 모든 Observer들에게 알림을 보낸다.

예시코드

subject.js

const createSubject = () => {
  const observers = new Map();

  const addObserver = (key, observer) => {
    if (!observers.has(key)) {
      observers.set(key, []);
    }
    observers.get(key).push(observer);
  };

  const removeObserver = (key, observer) => {
    if (observers.has(key)) {
      const filtered = observers.get(key).filter((obs) => obs !== observer);
      observers.set(key, filtered);
    }
  };

  const notify = (key, data) => {
    if (observers.has(key)) {
      observers.get(key).forEach((observer) => observer(data));
    }
  };

  const subscribe = (key, callback) => {
    addObserver(key, callback);
    return () => removeObserver(key, callback);
  };

  return { notify, subscribe };
};

export const subject = createSubject();

observer.js

import { useState, useEffect } from "react";
import { subject } from "./Subject";

export const useCounterObserver = () => {
  const [state, setState] = useState(0);

  useEffect(() => {
    const unsubscribe = subject.subscribe("counter", setState);

    return () => unsubscribe();
  }, []);

  // 상태를 업데이트하고 구독자들에게 알림을 주는 함수
  const increment = () => {
    const newState = state + 1;
    subject.notify("counter", newState); // 'counter' 키에 대해 알림
  };

  return [state, increment];
};

App.js

import React from "react";
import { useCounterObserver } from "./useCounterObserver";

const CounterComponent = () => {
  const [count, increment] = useCounterObserver();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <h1>React Observer Pattern with Closures</h1>
      <CounterComponent />
      <CounterComponent />
    </div>
  );
};

export default App;

Publish-Subscribe


구성 요소

  • Publisher (발행자): 특정 이벤트를 발생시키는 역할을 한다. Publisher는 어떤 Subscriber가 있는지 알 필요가 없다.
  • Subscriber (구독자): 이벤트를 구독하고, 특정 이벤트가 발생했을 때 알림을 받는 역할을 한다.
  • Event Channel (이벤트 채널): 이벤트를 전달하는 중간 매개체 역할을 한다. Publisher는 Event Channel을 통해 이벤트를 발행하고, Subscriber는 Event Channel을 통해 이벤트를 구독하여 알림을 받는다.

동작 방식

  • 중간 매개체를 통한 간접적인 의존 관계: Pub-Sub 패턴에서는 Publisher와 Subscriber가 서로 직접적으로 알지 못한다. Publisher는 Event Channel을 통해 메시지를 발행하고, Subscriber는 특정 채널을 구독하여 이벤트를 받는다.
  • 비동기적 처리 가능: Pub-Sub는 이벤트가 즉시 전달되지 않고, 중간에 저장되거나 비동기로 처리될 수 있다.

특징

  • 느슨한 결합: Publisher와 Subscriber는 서로에 대해 전혀 알지 못하고, 이벤트 채널을 통해서만 통신한다.
  • 확장성: 여러 개의 채널을 구독할 수 있고, 여러 Subscriber가 같은 이벤트를 구독할 수 있다.

예시코드

event.js

const event = {
  list: new Map(),
  on(eventType, eventAction) {
    this.list.has(eventType) || this.list.set(eventType, []);
    if (this.list.get(eventType)) this.list.get(eventType).push(eventAction);
    return this;
  },

  emit(eventType, ...args) {
    this.list.get(eventType) &&
      this.list.get(eventType).forEach((cb) => {
        cb(...args);
      });
  }
};

Modal.js

export const Modal = () => {
  const [content, setContent] = useState("no content");
  const [showModal, setShowModal] = useState(false);

  const setMessage = (message) => {
    setContent(message);
    setShowModal(true);
  };
  const clearMessage = () => {
    setContent("");
    setShowModal(false);
  };

  useEffect(() => {
    event.on("showModal", setMessage).on("clearAllMessage", clearMessage);
  }, []);
  if (showModal) {
    return (
      <Container>
        <h2>{content}</h2>
        <button onClick={clearMessage}>Close Modal </button>
      </Container>
    );
  }
  return null;

publisher.js

export const ModalPublisher = {
  message: (content) => {
    event.emit("showModal", content);
  },
  clearAllMessage: () => {
    event.emit("clearAllMessage");
  }
};

App.js

function App() {
  return (
    <div className="App">
      <Modal showModal={true} />
      <h1
        onClick={() => {
          ModalPublisher.message("this is the content from App");
        }}
      >
        Hello CodeSandbox
      </h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Difference

항목Observer 패턴Pub-Sub 패턴
구성 요소Subject, ObserverPublisher, Subscriber, Event Channel
관계Subject가 Observer를 알고 있어야 하며, 직접 통지함Publisher와 Subscriber는 서로 모름, 중간에 Event Channel이 존재
결합도강한 결합 (Subject와 Observer 간)느슨한 결합 (Publisher와 Subscriber는 서로 독립적)
알림 방식Subject가 직접 Observer에게 알림Event Channel을 통해 간접적으로 메시지를 전달
처리 방식일반적으로 동기적으로 알림비동기적으로 이벤트 전달 가능

Conclusion

  • Observer 패턴: 직접적인 통신 방식으로, Subject가 Observer를 알고 있어야 하고, 상태 변화 시 직접 알림을 보낸다.
  • Pub-Sub 패턴: 간접적인 통신 방식으로, Publisher와 Subscriber가 서로 몰라도 되고, 중간 Event Channel을 통해 메시지가 전달된다.

Reference

velog/sondev
dev
스파르타코딩클럽

profile
선한 영향력을 줄 수 있는 개발자가 되자, 되고싶다.

0개의 댓글