옵저버 패턴에 대해 알아보자

Design Pattern

이 글은 2024-06-02에 작성되었어요.

프롤로그


옵저버 패턴은 유명한 디자인 패턴입니다.

제가 디자인 패턴 관련 포스팅을 한 것은 처음이지만 옵저버 패턴을 처음으로 뽑아봤습니다.

옵저버 패턴이 IntersectionObserverAPI 또는 여러 라이브러리에서 많이 사용되고 있다는 점 때문에 정리해두면 좋은 패턴이라 생각했습니다.

그래서 오늘은 이 옵저버 패턴에 대해서 정리해보고, 읽는 분들께 제가 아는 한 잘 전달해서 도움이 되었으면 하여 포스팅을 하게 되었습니다.

옵저버 패턴


옵저버 패턴은 관찰 가능한 객체를 구독하며 이벤트가 발생할 때 마다 구독한 모든 관찰자에게 알리는 코드를 작성하는 방식을 뜻합니다.

단순히 개념을 정리하자니 꽤나 복잡한 느낌이 드는데요. 사실 예시나 시각적 자료가 있다면 이해가 팍팍되는 꽤나 쉬운 패턴입니다.

그러니 먼저 자료부터 볼까요?

사용자가 handleClick 또는 handleToggle 함수를 호출할 때마다 notify 관찰자에서는 구독자들에게 알림을 보냅니다.

아직도 이해가 어려우시다면 예시를 하나 들어볼까요?

보시면 ‘구독자’라는 개념이 아까부터 반복되고 있습니다. 어쩐지 뭔가 익숙한 단어이지 않나요?

여러분이 자주 접하시는 유튜브의 구독자랑 비슷한 개념으로 이해하셔도 좋습니다!

  • 구독하는 주체 (Observer) - 유튜버 구독자
  • 구독 가능한 객체 (Observable) - 유튜브 채널

이제 이해가 조금 되셨을거라 믿고 🙂 코드로 넘어가겠습니다.

코드로 보는 옵저버 패턴


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)); } }

옵저버 패턴을 클래스 형태로 구현한 코드입니다. 이를 컴포넌트와 결합해 볼까요?

function logger(data) { console.log(`${Date.now()} ${data}`); } observable.subscribe(logger); export default function App() { function handleClick() { observable.notify("User clicked button!"); } return ( <div className="App"> <Button onClick={handleClick}>Click me!</Button> </div> ); }

logger 라는 함수가 구독자 리스트에 추가되면 handleClick에 의해 구독자들에게 알림을 날리게 되고 이 ‘알림’이 logger 실행으로 이어지게 됩니다.

물론 이는 이해를 돕기 위해 기초적인 구현이니 참고로 봐주세요. 😃

마무리


옵저버 패턴을 사용하면 Observer와 Observable 간의 직접적인 의존성을 줄일 수 있어서 서로의 결합도를 낮출 수 있는 장점이 있습니다. 이를 활용한 예시로는 관찰자(상태)가 변경되면 이를 구독자(컴포넌트)가 업데이트하는 상태 관리 라이브러리가 있어요. 또 브라우저의 이벤트 시스템(ex. addEventListener) 등이 이 패턴을 기반으로 하고 있어요.

다만 유튜브 채널처럼 현재 보지도 않는 채널을 계속 구독하고 있는 현상이 반복되면 메모리 누수가 생길 수도 있으니 조심해서 사용해야 겠네요. 이 외에도 옵저버가 많아지면 어떤 옵저버가 상태를 변경시켰는지 디버깅하는 과정이 어려워져 유지보수가 힘들어지게 될 수 도 있습니다.

잘 사용하면 정말 좋은 패턴이지만 그만큼 메커니즘 설계를 잘 해야 하는 재미있는 패턴이네요!