Observer 패턴

kim yeeun·2023년 11월 22일
1

1) 옵저버 패턴이란?

감시자 패턴이라고도 부르며, 크게 관찰자인 옵저버와 관찰 대상 객체로 구성되어 있다.

관찰 대상 객체에 옵저버(관찰자) 목록을 등록해두고, 객체의 상태가 변할 때마다 각 옵저버에게 변경 알림을 준다.

만약 A객체의 상태가 변할 때 B함수를 실행시키고 싶다면, 이 옵저버 패턴을 이용하면 된다.

옵저버 패턴의 예시로 요소의 이벤트를 감지하는 addEventListener가 있다.

const btn = document.querySelector('.btn'); // btn에 click이벤트시 어떤 로직을 실행 
btn.addEventListener("click", function() { ... })

2) 옵저버 패턴의 구성

(1) 옵저버(관찰자)

객체의 변화를 감지할 감시자이다.

객체의 상태 변화가 있을 때 동작할 메소드를 가진다. (ex. update())

여러 옵저버가 필요할 시, 옵저버 클래스를 만들고 이 클래스를 상속 받아 옵저버1, 2를 만들 수 있다.

(2) 객체(관찰대상)

객체는 해당 객체의 변화를 감시하는 옵저버 리스트를 저장하고 있다.

subscribe()를 사용해 옵저버를 추가한다.

객체 상태가 변하면(ex. 상태값 변경, 이벤트 발생) notify()를 사용해 옵저버에게 알려준다.

옵저버는 notify()를 통해 객체 상태 변화를 감지하고, update()를 실행한다.

3) 옵저버 예시

첫째, 둘째, 셋째 자식 옵저버가 있으며, 엄마의 상태가 변했을 때 세 옵저버가 콘솔을 실행하도록 하게 하자.

(1) 옵저버 베이스 클래스 만들기

먼저, 옵저버 클래스를 만든다.

class Observer { update(v) {} }

(2) 여러 옵저버 만들기

그리고 첫째, 둘째, 셋째 옵저버는 Observer를 상속 받아 생성한다.

class Observer1 extends Observer {
  update(isWaiting) {
    if (!isWaiting) console.log('첫째는 집에 갈거야')
  }
}
class Observer2 extends Observer {
  update(isWaiting) {
    if (!isWaiting) console.log('첫째는 집에 갈거야')
  }
}
class Observer3 extends Observer {
  update(isWaiting) {
    if (!isWaiting) console.log('첫째는 집에 갈거야')
  }
}

(3) 관찰 대상 클래스 만들기

Mom 클래스를 만든다.

Mom 클래스는 자신의 변화를 감지할 옵저버 리스트(children)을 가진다.

class Mom {
  constructor() {
    this.children = [];          // 객체의 변화를 감지할 옵저버 리스트
    this.waiting = true;
  }
  subscribe(child) {             // 감시할 옵저버 구독하기
    this.children.push(child)
  }
  unsubscribe(removedChild) {    // 옵저버 비구독하기
    this.children = this.children.filter(child => child !== removedChild)
  }
  notify() {                     
    this.waiting = false;                 // 객체의 상태 변경
    for (let child of this.children) {    
      child.update(this.waiting);         // 객체 상태 변경에 따른 옵저버의 로직 실행 
    } 
  }
}

####(4) 옵저버 구독하기

mother객체를 관찰할 옵저버를 구독해준다.

// 관찰 대상 객체 생성
const mother = new Mom();

// 옵저버 객체 생성
const child1 = new Observer1();
const child2 = new Observer2();
const child3 = new Observer3();

// 구독
mother.subscribe(child1);
mother.subscribe(child2);
mother.subscribe(child3);

(5) 변화에 따른 로직 실행하기

mother은 notify()를 사용해, 관찰자에게 객체 상태 변화(waiting 변수 변경)를 알린다.

관찰자인 옵저버는 객체 상태 변경을 감지하고, 특정 로직을 실행한다.

mother.notify();  // mom를 구독하는 모든 관찰자에게 알려줌

4) 옵저버 패턴은 어떤 상황에 유용할까?

(1) 모듈 간의 의존성을 낮출 때 유용하다.

JS에서 코드를 설계할 때 모듈의 범위를 세부적으로 나누는 것이 중요하다.

또한 각 모듈간의 상호 의존성을 줄이는 게 좋다. (의존성이 크면 문제 발생시 핸들링해야 하는 규모가 커짐)

하지만 만약 A, B 모듈이 서로의 데이터가 필요한 경우에는 어떻게 해야할까?

A, B 모듈을 합치는 건 상호 의존성을 높이며, 이는 유지 보수 측면에서 좋지 않다.

이 경우에는 모듈을 합치는 대신 옵저버 패턴을 이용하면 좋다.

먼저, A, B 모듈이 서로 필요로 하는 데이터의 변화를 감지하는 관찰자 객체(옵저버)를 만든다.

그리고 데이터가 변경될 때마다 각 모듈은 관찰자에게 데이터와 상태 변경을 통보한다.

관찰자는 데이터 변경에 따른 로직을 실행하면 된다.

(단, 모듈에서는 어떤 상태 변화에 어떤 로직을 실행할지 미리 옵저버에 등록해야 함)

(2) polling을 방지할 수 있다.

polling이란, 상태를 주기적으로 확인하고 만약 조건을 만족할 시 자료처리를 하는 방식이다.

polling의 단점은 짧은 주기로 관찰하면 부하가 발생하며, 긴 주기로 관찰하면 실시간성이 떨어진다는 것이다.

하지만, 옵저버 패턴을 사용하게 되면 관찰 대상의 상태가 변경됐을 때를 감지할 수 있으므로 polling을 사용하지 않아도 된다.

profile
안녕하세요 프론트엔드 엔지니어 김예은입니다.

0개의 댓글