멘토님이 추천해주신 디자인 패턴 사이트 중에서 평소 알고 싶었던 옵저버 패턴에 대해서 학습해보았습니다.
갑자기 스타크래프트 옵저버가 생각나서... 이 옵저버는 아닙니다 (이 개그에 불쾌감을 느끼신 분들이 있으시다면 사과의 말씀 드립니다...)
Observable을 활용해 Subscriber에게 이벤트 발생을 알린다
Observer
구독하는 주체Observable
구독 가능한 객체Observable
은 모든 Observer
에게 이벤트를 전파한다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에게 이벤트를 전파할 수 있다.
사용자가 Button
을 클릭하거나 Switch
를 토글할 때 타임스탬프를 로깅하고자 하며, 이벤트 발생시마다 토스트 알림을 화면에 노출하려고 한다.
구현해야 되는 로직은
handleClick
또는 handleToggle
함수를 호출할 때 마다 핸들러는 Observable의 notify
를 호출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
함수는 알림을 받게 되며, handleClick
과 handleToggle
이벤트 핸들러에서 notify
를 호출하는 것을 통해 이벤트를 전파한다. 이 때 Observer에서 필요한 데이터를 인자로 전달한다.
전체 플로우를 살펴보면 아래와 같다.
handleClick
과 handleToggle
이 Observable의 notify
를 호출 => 이를 구독하고 있던 Observer (Observers배열) logger
와 toastify
함수는 이 이벤트를 받아서 특정 동작을 수행한다. 앱 내에서 인터렉션(상호작용)이 발생하는 동안 logger
와 toastify
는 notify
의 호출로부터 이벤트를 계속 받을 수 있다.
Observer 패턴은 다양하게 활용할 수 있지만 비동기 호출이나 이벤트 기반 데이터를 처리할 때 매우 유용하다. 만약 어떤 컴포넌트가 특정 데이터의 다운로드 완료 알림을 받기 원하거나, 사용자가 메시지 보드에 새로운 메시지를 게시했을 때 모든 멤버가 알림을 받거나 하는 등의 상황을 말한다.
RxJS는 Observer 패턴을 구현한 유명 오픈소스 라이브러리이다
ReactiveX 는 Observer 패턴, 이터레이터 패턴, 함수형 프로그래밍을 조합하여 이벤트의 순서를 이상적으로 관리할 수 있다.
RxJS를 사용하면 Observable과 Observer(Subscriber)를 만들어낼 수 있다. 아래 예제는 공식 문서에 소개된 것으로 사용자가 문서를 드래그 중인지 아닌지 콘솔에 출력해준다.
RxJS는 이것 말고도 Observer 패턴에 대한 매우 많은 빌트인 기능들을 제공한다.
Observer 패턴을 사용하는 것도 관심사의 분리와 단일 책임의 원칙을 강제하기 위한 좋은 방법이다. Observer 객체는 Observable 객체와 강결합되어있지 않고 언제든지 분리될 수 있다. Observable 객체는 이벤트 모니터링의 역할을 갖고. Observer는 받은 데이터를 처리하는 역할을 갖게 된다.
Observer가 복잡해지면 모든 Observer들에 알림을 전파하는 데 성능 이슈가 발생할 수 있다.