개발 기술/개발 이야기

디자인 패턴1, 옵저버란?

by GicoMomg (Lux) 2021. 12. 8.

1. 디자인 패턴이란?

  • JS에는 문제 해결을 위한 7가지 디자인 패턴이 존재한다.
  • 우리는 개발을 하면서 여러 상황에 직면하게 되고, 상황을 해결하기 위해 적지 않은 시간을 소모한다.
  • 하지만 만약 이 상황을 해결하기 위한, 접근 방향을 알 수 있다면?
  • 우리는 아마 해결을 위해 소모되는 시간을 줄일 수 있을 것이다.

문제 해결에 방향성을 제시하는 이론이 없을까? 디자인 패턴을 봐라!

  • JS 디자인 패턴특정 상황을 해결하기 위해 어떤 패턴을 사용하면 좋을지에 대한 방향성을 제시한다.
  • 단 유념해야할 점은 어디까지나 방향을 안내하는 역할을 하지, 구체적인 솔루션을 제공치 않는다.
  • 디자인 패턴에는 Constructor, Prototype, Command, Observer, Singleton, Module, Factory 패턴이 존재하며, 이번 시간에는 이 중 옵저버 패턴(Observer)에 대해 설명한다.




2. 디자인 패턴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을 사용하지 않아도 된다.
반응형

댓글