참고 사이트

멘토님이 추천해주신 디자인 패턴 사이트 중에서 평소 알고 싶었던 옵저버 패턴에 대해서 학습해보았습니다.

갑자기 스타크래프트 옵저버가 생각나서... 이 옵저버는 아닙니다 (이 개그에 불쾌감을 느끼신 분들이 있으시다면 사과의 말씀 드립니다...)

Observer 패턴

Observable을 활용해 Subscriber에게 이벤트 발생을 알린다

  • Observer 구독하는 주체
  • Observable 구독 가능한 객체
  • 이벤트가 발생할 때마다 Observable은 모든 Observer에게 이벤트를 전파한다

Observable 객체

Observable 객체는 (보통) 3가지 주요 특징을 포함한다.

  • observers 이벤트가 발생할 때마다 전파할 Observer들의 배열
  • subscribe() Observer를 Observer 배열에 추가한다
  • unsubscribe() Observer배열에서 Observer를 제거한다
  • notify() 등록된 모든 Observer들에게 이벤트를 전파한다
class Observable {
  constructor() {
    this.observers = []
  }

  subscribe(func) {
    this.observers.push(func)
  }

  unsubscribe(func) {
    this.observers = this.observers.filter(observer => observer !== func)
  }

  notify(data) {
    this.observers.forEach(observer => observer(data))
  }
}

subscribe 메서드를 통해 Observer를 등록하고, unsubscribe를 통해 등록을 해지할 수 있다.
notify 메서드를 통해 모든 Observer에게 이벤트를 전파할 수 있다.

Observable 객체를 활용해보기

  • 리액트

사용자가 Button을 클릭하거나 Switch 를 토글할 때 타임스탬프를 로깅하고자 하며, 이벤트 발생시마다 토스트 알림을 화면에 노출하려고 한다.

구현해야 되는 로직은

  1. 사용자가 handleClick 또는 handleToggle 함수를 호출할 때 마다 핸들러는 Observable의 notify를 호출
  2. notify 메서드는 등록된 모든 Observer들에게 handleClick혹은 handleToggle에서 전달된 데이터를 포함한 이벤트를 전파

와 같다.

import { ToastContainer, toast } from 'react-toastify'

function logger(data) {
  console.log(`${Date.now()} ${data}`)
}

function toastify(data) {
  toast(data)
}

observable.subscribe(logger)
observable.subscribe(toastify)

export default function App() {
  function handleClick() {
    observable.notify('User clicked button!')
  }

  function handleToggle() {
    observable.notify('User toggled switch!')
  }

  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
      <ToastContainer />
    </div>
  )
}

Observable의 subscribe 메서드를 사용해서 logger, toastify 함수를 연결해서 Observer로서 작동할 수 있도록 해준다. 이벤트가 발생할 때 마다 logger, toastify 함수는 알림을 받게 되며, handleClickhandleToggle 이벤트 핸들러에서 notify를 호출하는 것을 통해 이벤트를 전파한다. 이 때 Observer에서 필요한 데이터를 인자로 전달한다.

전체 플로우를 살펴보면 아래와 같다.

handleClickhandleToggle이 Observable의 notify를 호출 => 이를 구독하고 있던 Observer (Observers배열) loggertoastify함수는 이 이벤트를 받아서 특정 동작을 수행한다. 앱 내에서 인터렉션(상호작용)이 발생하는 동안 loggertoastifynotify의 호출로부터 이벤트를 계속 받을 수 있다.

활용할 수 있는 사례

Observer 패턴은 다양하게 활용할 수 있지만 비동기 호출이나 이벤트 기반 데이터를 처리할 때 매우 유용하다. 만약 어떤 컴포넌트가 특정 데이터의 다운로드 완료 알림을 받기 원하거나, 사용자가 메시지 보드에 새로운 메시지를 게시했을 때 모든 멤버가 알림을 받거나 하는 등의 상황을 말한다.

RxJS

RxJS는 Observer 패턴을 구현한 유명 오픈소스 라이브러리이다

ReactiveX 는 Observer 패턴, 이터레이터 패턴, 함수형 프로그래밍을 조합하여 이벤트의 순서를 이상적으로 관리할 수 있다.

RxJS를 사용하면 Observable과 Observer(Subscriber)를 만들어낼 수 있다. 아래 예제는 공식 문서에 소개된 것으로 사용자가 문서를 드래그 중인지 아닌지 콘솔에 출력해준다.

RxJS는 이것 말고도 Observer 패턴에 대한 매우 많은 빌트인 기능들을 제공한다.

Observer 패턴의 장점과 단점

장점

Observer 패턴을 사용하는 것도 관심사의 분리와 단일 책임의 원칙을 강제하기 위한 좋은 방법이다. Observer 객체는 Observable 객체와 강결합되어있지 않고 언제든지 분리될 수 있다. Observable 객체는 이벤트 모니터링의 역할을 갖고. Observer는 받은 데이터를 처리하는 역할을 갖게 된다.

단점

Observer가 복잡해지면 모든 Observer들에 알림을 전파하는 데 성능 이슈가 발생할 수 있다.

참조

profile
$ npm run dev:ryan

0개의 댓글