본문 바로가기

옵저버 패턴

by Recstasy 2022. 12. 8.

옵저버 패턴이 사용되지 않는 프로그램은 거의 없다. 프로그램 내 '메시지 호출'과 같은 알람은 모두 옵저버 패턴을 사용하기 때문이다. 사실상 스마트폰을 소유한 사람들은 매일 옵저버 패턴을 경험하는 셈이다.

 

옵저버 패턴은 '언론사-구독자' 패턴이라 불린다. 구독자는 언론사에 구독 신청을 한다. 그리고 언론사는 구독자를 등록한다. 이후, 언론사는 업데이트 된 정보를 모든 구독자에게 알린다. 만일, 해지를 요청하는 구독자가 있다면, 즉시 구독자 명단에서 제외한다. 이게 옵저버 패턴의 전부다.   

 

'언론사-구독자'에서 언론사는 iSubject추상 클래스를 상속받고, 구독자는 iObserver추상 클래스를 상속받는다. 보통 '데이터(정보)'가 있는 객체가 언론사(Subject)가 되며, 화면을 보여주는 View영역이 구독자(Observer)가 된다. 이로써 실시간으로 정보가 변경될 때마다 화면이 갱신되는 시스템은 모두 옵저버 패턴을 따른다고 볼 수 있다.

 

 

가령, 기차 도착정보(Subject)는 코레일 앱을 설치한 모든 구독자(observer)가 볼 수 있다. 또, 기차도착 정보가 변경될 때마다 구독자의 기기(어플, 웹사이트, 전광판, 등..)의 화면이 갱신된다.(observer.update()실행) 만일, MVC패턴과 옵저버를 혼합한다면, M(모델)쪽에 Subject, V(뷰)가 Observer가 될 수 있다.

 

 

이론은 여기까지. 이제 옵저버 패턴을 이용한 실시간 지하철 도착정보 프로그램을 제작해보자.

 

 

 


1 「인터페이스(subject, observer) 생성」

우선 iSubject, iObserver 클래스가 필요하다. '옵저버 패턴에서 'Subject 추상클래스'는 반드시 '등록(register), 해지(remove), 알림(notify)'기능이 필요하다.

 

 
  export class iSubject {

      constructor() {
          this.observers = null;
      }

      register() {
          throw new Error('U must code this');
      }

      remove() {
          throw new Error('U must code this');
     }

      notify() {
          throw new Error('U must code this');
      }

      toString() {
          return `AbstractSubject`;
      }

  }

[iSubject.js]

 

옵저버 추상클래스도 생성한다. 옵저버 인터페이스에서 중요한 기능은 '갱신(update)'이다. subject클래스가 각 옵저버들에게 update명령을 내렸을 때, 옵저버들은 각자 상황에 따라 갱신한다.

 


  export
class iObserver {
      constructor( operationSubject ) {
          this.operationSubject = operationSubject;
      }

      update() {
          throw new Error(`U must code this`);
      }

      getStation() {
          return this.operationSubject.getCurrentStation();
      }

      toString() {
          return `iObserver`;
      }

  }

[iObserver.js]

 

 

 

 

 


2 언론사 생성(subject 구현)

subject는 기차도착 정보를 입력받고, observer가 요청한 정보를 처리해준다. 즉, 'set', 'get'처리는 subject의 필수 메서드다.

 

 
  import { iSubject } from './iSubject.js'

  class subwaySubject extends iSubject {
      constructor() {
          super();
          this.station = null;
          this.observers = [];
      }

      register( observer) {
          this.observers.push( observer );
      }

      remove( observer ) {
          let idx = this.observers.indexOf(observer);
          this.observers.slice( idx, 1);
      }

      notify() {
          for(let ob of this.observers) {
              ob.update();
          }
      }

      setStation(station) {
          this.station = station;
          this.notify();
      }

      getCurrentStation() {
          console.log('정차역:'+this.station);
          return this.station;
      }

  }

  export { subwaySubject }

[subwaySubject.js]

 

 

 

 

 

 


3 옵저버 구현

subject의 등록, 제거, 알림 그리고 'get'을 요청하는 옵저버를 구현한다. 첫번째 옵저버는 phoneA를 사용하는 구독자다.

 

 
  import { iObserver } from './iObserver.js';

  export class phoneA extends iObserver {

      constructor(operation) {
          super(operation);
          this.phoneName = '아이폰14';
      }

      update() {
       
          let station = this.getStation();
          let result = `이번 역은 ${station} 입니다.  -Apple ${this.phoneName}-`
          this.screen( result );
      }

      screen( res ){
          console.log(res)
      }
  }

[phoneA_Observer.js]

 

구독자는 반드시 옵저버 인터페이스(iObserver.js)를 구현하며, getStation()메서드를 통해 정보를 받는다. 이 부분은 중요하다. 설계의 유연성을 주기 때문이다. 각 옵저버들은 옵저버 인터페이스의 'getStation()'을 통해서만 정보에 접근할 수 있다.(의존성 역전) 이로써 인터페이스가 변경되지 않는 한 시스템이 다운될 일이 없다.

 

나머지 옵저버 구현은 phoneA_Observer.js와 거의 유사하다.


  import
{ iObserver } from './iObserver.js';

  class phoneB extends iObserver {

      constructor(operation) {
          super(operation);
          this.phoneName = '노트20울트라';
      }

      update() {
         let station = this.getStation();
          let result = `이번 지하철 역은 ${station} 입니다.  -Samsung ${this.phoneName}-`
          this.screen( result );
      }

      screen( res ) {
          console.log( res );
      }
  }

  export { phoneB }

[phoneB_Observer.js]

 

각 옵저버들은 자신들의 폰 기종에 맞게 update()를 진행한다.

 

  import { iObserver } from './iObserver.js';

  class phoneC extends iObserver {

      constructor(operation) {
          super(operation);
          this.phoneName = '모토로라 스타텍'
      }

      update() {
          let station = this.getStation();
          let result = `이번에 정차하는 지하철 역은 ${station} 입니다.  -Hello Moto ${this.phoneName}-`
          this.screen( result );
      }

      screen( res ) {
          console.log( res );
      }
  }

  export { phoneC }

[phoneC_Observer.js]

 

 

 

 

 


4 옵저버 패턴 구현

subject(subwaySub)객체의 데이터를 지정할 때마다(setStation) notify()가 실행된다. 이로써 옵저버들은 모두 update()의 결과값을 받아볼 수 있다.

 

 
  <body>
      <script type="module">
          import { subwaySubject } from './subwaySubject.js';
          import { phoneA } from './phoneA_Observer.js';
          import { phoneB } from './phoneB_Observer.js';
          import { phoneC } from './phoneC_Observer.js';

        //옵저버 등록 & 서브젝트 연결
          let subwaySub_ = new subwaySubject();
          let phoneA_ = new phoneA( subwaySub_ );
          let phoneB_ = new phoneB( subwaySub_ );
          let phoneC_ = new phoneC( subwaySub_ );

          subwaySub_.register( phoneA_ );
          subwaySub_.register( phoneB_ );
          subwaySub_.register( phoneC_ );
       
        //신도림역 도착 알림
          subwaySub_.setStation( '신도림역' );
        // subwaySub_.setStation( '서울역' );

      </script>
  </body>

[client.html]

 

지하철에 신도림에 도착했을 때, 구독자(옵저버) 'phoneA, phoneB, phoneC'는 아래와 같은 알림 메시지를 받는다.

 

 

또, 'client.html'에서 setStation('서울역') [지하철이 서울역에 도착]을 실행한다면, 아래와 같은 메시지가 각 옵저버에게 전달된다.

 

 

위의 옵저버 패턴을 응용한다면, 여러가지 '구독자-언론사' 프로그램을 제작할 수 있다.

댓글

최신글 전체

이미지
제목
글쓴이
등록일