javascriptでObserverパターン


昨今のフロントエンド技術は進歩が圧倒的に早く、
jQueryわかります!MV*フレームワーク使ったことあります!
という程度ではもはや技術についていけなくなってしまっているこの頃です。

技術に追いついていけるだけの基礎体力をつけるべく、GUIプログラムにおけるMVCパターンのデザインパターンについて学習したことを書いていきます。

まずはObserverパターンから…
と思ってGUIにおけるMVCをいろいろ調べていたのですが、必須なのはObserverパターン(とその派生)だけだったという結論に至ったのでこれ以上javascriptのデザインパターンについては投稿するのやめました。

MVCについては、下記投稿や、
smalltalkのMVCについて目を通しておけば大丈夫だと考えます。

MVC、本当にわかってますか?
smalltalk mvc

投稿者のバックグラウンド

参考までに書いとく。

  • WEBアプリケーションのサーバサイドプログラムメインに開発
  • フロントエンドはMV*フレームワークなど利用できるが、概念までは説明出来ないゆるふわ系
  • GUIプログラムはオブジェクト指向のない言語でなら経験あり

Observerパターンとは?

イベントによって状態を更新するObserverと、そのObserverの登録や削除、イベントの通知を行うSubjectから構成される。

Subjectは登録されたObserverに対して通知を行うだけでよく、
Observerが何であるかということに関心を持たなくて良い。

と小難しく書いたが、要はSubjectとObserverに分けることでイベントの対象とイベントの発行が疎結合に出来るデザインパターン。

他に、Observerの状態に一貫性を持たせることが出来るという効用もあるようです。

サンプルプログラムで見てみる

ボタンをクリックするとボックスのカラーが変わるというプログラムを想定。
ボタン1をクリックすると赤に、
ボタン2をクリックすると青に、
ボタン3をクリックすると赤に変わって青に変わる。

Observerパターンを使わないプログラム

btn1 = document.getElementById 'btn1'
btn2 = document.getElementById 'btn2'
btn3 = document.getElementById 'btn3'

box1 = document.getElementById 'box1' 
box2 = document.getElementById 'box2'

btn1.onclick = () ->
  to_red = 'red'

  box1.style.backgroundColor = to_red
  box2.style.backgroundColor = to_red

btn2.onclick = () ->
  to_blue = 'blue'

  box1.style.backgroundColor = to_blue
  box2.style.backgroundColor = to_blue

btn3.onclick = () ->
  to_red = 'red'
  to_blue = 'blue'

  box1.style.backgroundColor = to_red
  box2.style.backgroundColor = to_red

  setTimeout () ->
    box1.style.backgroundColor = to_blue
    box2.style.backgroundColor = to_blue
  , 500

各クリックイベントでは、イベントの対象となるオブジェクトを知っていなければ処理が書けないことが分かります。

Observerパターンを適用する

SubjectとObserverのオブジェクトを定義

class Subject
  _observers = []

  add: (observer) ->
    _observers.push(observer)

  notify: (context) ->
    for observer in _observers
      observer.update(context)

class Observer
  update: (context) ->
  • Subject

Observerの登録と、登録したObserverに対してイベントの通知を行う。
(本当はSubjectオブジェクトには登録したObserverを削除するメソッドも必要ですが、今回のサンプルでは利用しないので省略しています。)

  • Observer

自身の状態を更新するメソッドを持ちます。

継承用関数の準備

上で定義したSubjectとObjectも抽象オブジェクトとして利用します。
そのため、継承用の関数を定義します。

extend = (obj, extension) ->
  for key, prop of obj
    extension[key] = obj[key]

実際の処理

btn1 = document.getElementById 'btn1'
btn2 = document.getElementById 'btn2'
btn3 = document.getElementById 'btn3'

for btn in [btn1, btn2, btn3]
  extend new Subject(), btn

box1 = document.getElementById 'box1' 
box2 = document.getElementById 'box2'

for box in [box1, box2]
  extend new Observer(), box

  box.update = (color) ->
    box.style.backgroundColor = color

  for btn in [btn1, btn2, btn3]
    btn.add box

btn1.onclick = () ->
  this.notify 'red'

btn2.onclick = () ->
  this.notify 'blue'

btn3.onclick = () ->
  self = this

  self.notify 'red'

  setTimeout () ->
    self.notify 'blue'
  , 500

各クリックイベントは、対象を知る必要がなくなり、
状態の通知のみを行えばよくなりました。

イベントの対象と発行が綺麗に分離できましたね

Observerパターンの派生

出版-購読型モデル
イベント駆動型プログラミング