observer pattern(옵저버 패턴) 이해하기

summer_joy·2022년 5월 24일
0

옵저버 패턴이란?

옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.

옵저버 패턴은 처음 접했을 때도 그렇고 지금 코드를 봐도 단번에 이해하기는 힘든 것 같다,,
특정 언어에 국한된 개념도 아니기 때문에 저렇게 범용적으로 표현이 되는데, 구글링을 통해 얻은 정보로 단순히 정의해보자면,
어떤 객체(뷰)가 변화할 때 이와 연관된 여러 객체에게 직접 통보하지 않고 중간관리자를 두어 변화의 감지와 통보를 대신하게 만드는 디자인 패턴 이라고 생각하면 이해가 쉬울 것 같다.

간단하게 투두리스트로 예를 들어보자

할 일이 목록으로 보여지고, 하단에 할 일의 총 개수를 출력해주는 화면이 있다고 생각해보자.
이 때, 할 일이 하나 추가되면 총 개수도 그에 맞게 변화해야 할 것이다.
그럼 단순하게 생각하면, 할 일이 추가되는 이벤트가 발생하고, 이벤트가 성공하면 total count 옆의 숫자를 +1된 값으로 바꾸면 된다. 반대로, 삭제되면 -1된 값으로 바꾸게 되는 것
지금은 단순하게 두 가지 뷰만 가지고 소통하고 있지만, 만약 이 할 일 목록이 변화할 때마다 함께 유기적으로 변해야 하는 ui요소가 5,개 10개가 된다면?
추가할 때마다 그 모든 뷰를 렌더링 하는 함수를 하나하나 불러와서 실행시켜주어야 하는데.. 이때부터 무언가 잘못되었다는 느낌이 들게 된다 ,,,,

무엇이 편해지는가?

이 때 옵저버 패턴이 등장하게 된다. 먼저 할 일 목록의 변화를 대신 감지하고 통보하는 중간관리자(publisher/발행하는 객체)를 만든다.
그리고 할 일 목록과 관련이 있는 모든 뷰는 '옵저버' 또는 '리스너'라고 부른다.
변화를 관찰하고 이에 맞게 대응하는 객체이기에 이런 명칭이 붙는다.

이 중간관리자가 할 일 목록의 변화를 감지하면 옵저버들의 렌더링 함수를 대신 실행시킨다. 이러면 할 일 목록 뷰에서 직접 다른 뷰로 이벤트를 하나하나 보낼 필요 없이, 중간관리자에게만 자신의 변화를 알리면 되는 것이다.

(물론 옵저버에게 최초 한 번은 어떤 변화/이벤트를 감지해야 하고, 이때 어떤 함수를 실행해야 할지 알리는 작업은 필요하다. 이를 subscribe 한다고 표현한다.)

그림으로 간단하게 변화를 표현해 본다면 이렇다.


이렇게 중간관리자가 대신 처리하게 하면 뷰끼리의 의존성이 없어지기 때문에 복잡한 구성에서도 각각의 뷰가 처리해야 되는 부분만 고민하면 된다.
즉, 객체끼리의 의존성이 낮아져 결합도가 낮아지기 때문에 유지보수에도, 확장성 면에서도 유리하다. 리액트에서도 상태의 변화를 감지하기 위해 하나하나 props로 내려주는 대신 상태관리를 통해 변화를 알리고 그 변화를 감지하여 다시 렌더링 되는 것과 비슷한 느낌이라고 생각하면 된다.

위키피디아의 설명된 그림을 참조해보면,
옆의 Subject가 옵저버를 등록한, 즉 관찰 대상이 되는 객체들이다.
이 객체에 이벤트가 발생하게 되면 옵저버에게 콜백 함수가 전달된다. 그림에선 notify() 함수가 되겠다.

notify는 옵저버가 정의한 메소드이다. 옵저버는 notify 함수를 통해 이벤트 발생 시 어떤 동작을 처리할 것인가를 결정한다.

// 서브젝트
class Subject {
  constructor() {
    this.observers = [];
  }
  // 옵저버 등록
  registerObserver(observer) {
    this.observers.push(observer);
  }
  // 옵저버 제거
  unregisterObserver(observer) {
    this.observers = this.observers.filter(
      (registedObserver) => registedObserver !== observer
    );
  }
  // 알려주는 함수
  notifyObservers(data) {
    // 함수로 넣었을 때
    // this.observers.forEach(observer => observer(data));
    this.observers.forEach((observer) => observer.notify(data));
  }
}

const subject$ = new Subject();

// 객체가 아닌 함수로 넣었을 때.
// const observer1 = data => console.log('first ' + data);
// const observer2 = data => console.log('second ' + data);
const observer1 = { notify: (str) => console.log("first " + str) };
const observer2 = { notify: (str) => console.log("second " + str) };

subject$.registerObserver(observer1);
subject$.registerObserver(observer2);

subject$.notifyObservers("noti");

위의 코드가 옵저버를 간단하게 구현한 코드이다.
서브젝트는 옵저버들을 가지는 배열과 옵저버를 등록하는 함수 그리고 제거하는 함수로 이루어져 있다.

굉장이 간단한 코드이지만 옵저버 패턴의 정의를 간략하게 이해할 수 있다.
서브젝트는 옵저버를 가지며, 옵저버에게 정보를 전달한다는 사실과
옵저버는 서브젝트에게 전달받은 데이터를 처리한다는 사실을 말이다.

참고
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
https://lihano.tistory.com/19

profile
💻 Hello world

0개의 댓글