オブザーバモードとパブリッシュ/サブスクリプションモード


オブザーバモードとパブリッシュ/サブスクリプションモード


オブザーバモード


コンセプト


1つの被観察者のオブジェクトは、登録によって1組の観察者オブジェクトを維持する.被観察者が変化すると,通知が生成され,ブロードキャストにより送信され,最後に各観察者の更新方法が呼び出される.観察者が被観察者の通知を受け入れる必要がなくなった場合、被観察者は、その観察者を維持されたグループから削除することができる.

インプリメンテーション


このインプリメンテーションには、次のコンポーネントが含まれます.
  • 被観察者:観察者のグループを維持し、観察者を増加および除去するための方法
  • を提供する.
  • オブザーバー:オブザーバーの状態が変化するときに通知を得るための更新インタフェースを提供する
  • .
  • 具体的な被観察者:状態変化時に観察者に放送通知し、具体的な観察者の情報
  • を保持する.
  • 特定の観察者:特定の観察者への参照を保持し、観察のための更新インタフェースを実現し、自身の状態が常に観察者の状態と一致することを保証する
  • .
  • まず、観察者によって維持された観察者のグループ(リスト)をモデリングする
    function ObserverList() {
      this.observerList = []
    }
    
    ObserverList.prototype.add = function(obj) {
      return this.observerList.push(obj)
    }
    
    ObserverList.prototype.Empty = function() {
      this.observerList = []
    }
    
    ObserverList.prototype.removeAt = function(index) {
      this.observerList.splice(index, 1)
    }
    
    ObserverList.prototype.count = function() {
      return this.observerList.length
    }
    
    ObserverList.prototype.get = function(index) {
      if (index > -1 && index < this.observerList.length) {
        return this.observerList[index]
      }
    }
    
    // Extend an object with an extension
    function extend(extension, obj) {
      for (var key in extension) {
        obj[key] = extension[key]
      }
    }
  • .
  • 次に、観察者およびその増加、削除、通知能力をモデリングする
    function Subject() {
      this.observers = new ObserverList()
    }
    
    Subject.prototype.addObserver = function(observer) {
      this.observers.add(observer)
    }
    
    Subject.prototype.removeObserver = function(observer) {
      this.observers.removeAt(this.observers.IndexOf(observer, 0))
    }
    
    Subject.prototype.notify = function(context) {
      var observerCount = this.observers.count()
    
      for (var i = 0; i < observerCount; i++) {
        this.observers.get(i).update(context)
      }
    }
  • .
  • 次に、観察者をモデリングし、ここでupdate関数はその後、
    function Observer() {
      this.update = function() {
        // ...
      }
    }
  • の特定の動作によって上書きする.

    サンプル適用


    上のオブザーバーコンポーネントを使用して定義します
  • ページに新しい観察者としての選択ボックスを追加するためのボタン
  • 制御用の選択枠である、観察者として機能し、他の選択枠が
  • で選択すべきか否かを通知する.
  • 新しい選択ボックス
    
    
    
    // DOM      
    var controlCheckbox = document.getElementById('mainCheckbox'),
      addBtn = document.getElementById('addNewObserver'),
      container = document.getElementById('observersContainer')
    
    //        
    
    // Subject     controlCheckbox
    extend(new Subject(), controlCheckbox)
    
    //   checkbox            
    controlCheckbox['onclick'] = new Function(
      'controlCheckbox.notify(controlCheckbox.checked)'
    )
    
    addBtn['onclick'] = AddNewObserver
    
    //       
    
    function AddNewObserver() {
      //             checkbox
      var check = document.createElement('input')
      check.type = 'checkbox'
    
      //    Observer     checkbox
      extend(new Observer(), check)
    
      //       update     
      check.update = function(value) {
        this.checked = value
      }
    
      //                         
      controlCheckbox.AddObserver(check)
    
      //            
      container.appendChild(check)
    }
  • を配置するための容器.
    上記の例では
  • SubjectクラスはcontrolCheckboxを拡張するので、controlCheckboxは具体的な被観察者
  • である.
  • addBtnをクリックすると、新しいcheckが生成され、checkはObserverクラスに拡張されupdateメソッドが書き換えられるので、checkは具体的な観察者であり、最後にcontrolCheckboxはcontrolCheckboxが維持する観察者リストにcheckを追加する
  • .
  • controlCheckboxをクリックすると、被観察者のnotifyメソッドが呼び出され、さらにcontrolCheckboxで維持する観察者のupdateメソッド
  • がトリガーされる.

    パブリッシュ/サブスクリプションモード


    インプリメンテーション

    var pubsub = {}
    
    ;(function(q) {
      var topics = {},
        subUid = -1
    
      // Publish or broadcast events of interest
      // with a specific topic name and arguments
      // such as the data to pass along
      q.publish = function(topic, args) {
        if (!topics[topic]) {
          return false
        }
    
        var subscribers = topics[topic],
          len = subscribers ? subscribers.length : 0
    
        while (len--) {
          subscribers[len].func(topic, args)
        }
    
        return this
      }
    
      // Subscribe to events of interest
      // with a specific topic name and a
      // callback function, to be executed
      // when the topic/event is observed
      q.subscribe = function(topic, func) {
        if (!topics[topic]) {
          topics[topic] = []
        }
    
        var token = (++subUid).toString()
        topics[topic].push({
          token: token,
          func: func
        })
        return token
      }
    
      // Unsubscribe from a specific
      // topic, based on a tokenized reference
      // to the subscription
      q.unsubscribe = function(token) {
        for (var m in topics) {
          if (topics[m]) {
            for (var i = 0, j = topics[m].length; i < j; i++) {
              if (topics[m][i].token === token) {
                topics[m].splice(i, 1)
                return token
              }
            }
          }
        }
        return this
      }
    })(pubsub)
    

    サンプル適用1

    // Another simple message handler
    
    // A simple message logger that logs any topics and data received through our
    // subscriber
    var messageLogger = function(topics, data) {
      console.log('Logging: ' + topics + ': ' + data)
    }
    
    // Subscribers listen for topics they have subscribed to and
    // invoke a callback function (e.g messageLogger) once a new
    // notification is broadcast on that topic
    var subscription = pubsub.subscribe('inbox/newMessage', messageLogger)
    
    // Publishers are in charge of publishing topics or notifications of
    // interest to the application. e.g:
    
    pubsub.publish('inbox/newMessage', 'hello world!')
    
    // or
    pubsub.publish('inbox/newMessage', ['test', 'a', 'b', 'c'])
    
    // or
    pubsub.publish('inbox/newMessage', {
      sender: '[email protected]',
      body: 'Hey again!'
    })
    
    // We cab also unsubscribe if we no longer wish for our subscribers
    // to be notified
    // pubsub.unsubscribe( subscription );
    
    // Once unsubscribed, this for example won't result in our
    // messageLogger being executed as the subscriber is
    // no longer listening
    pubsub.publish('inbox/newMessage', 'Hello! are you still there?')

    サンプル適用2


    古いコード
    $.ajax('http:// xxx.com?login', function(data) {
      header.setAvatar(data.avatar) //    header      
      nav.setAvatar(data.avatar) //          
    })

    パブリケーション/サブスクリプションモードを使用したコード
    $.ajax('http:// xxx.com?login', function(data) {
      pubsub.publish('loginSucc', data) //          
    })
    
    // header   
    var header = (function() {
      pubsub.subscribe('loginSucc', function(data) {
        header.setAvatar(data.avatar)
      })
    
      return {
        setAvatar: function(data) {
          console.log('   header      ')
        }
      }
    })()
    
    // nav   
    var nav = (function() {
      pubsub.subscribe('loginSucc', function(data) {
        nav.setAvatar(data.avatar)
      })
    
      return {
        setAvatar: function(avatar) {
          console.log('   nav      ')
        }
      }
    })()

    メリット

  • は、ajax要求のsucc、errorなどのイベントや、アニメーションのフレームごとに完了した後にイベントをパブリッシュするなどの非同期プログラミングに適用することができ、これにより、再非同期実行中の内部状態
  • を過度に注目する必要がない.
  • は、オブジェクト間のハードコーディングの通知メカニズムに取って代わる、1つのオブジェクトが他のオブジェクトのインタフェースを表示せずに呼び出し、2つのオブジェクトが緩やかに結合するように
  • に関連付ける.

    欠点

  • サブスクライバを作成するには、メッセージをサブスクライバすると、メッセージが最後まで発生しなくても、このサブスクライバは常にメモリに存在します.
  • パブリケーション/サブスクリプションモードは、オブジェクト間の連絡を弱め、オブジェクトとオブジェクト間の必要な連絡も背後に埋め込まれ、プログラムが
  • のメンテナンスと理解を追跡することが困難になる.

    両者の相違

  • オブザーバーモードは、関連通知を受けるオブザーバーが、このイベントを開始するオブザーバーにこのイベント
    controlCheckbox.AddObserver(check)
  • を登録しなければならないことを要求する.
  • パブリケーション/サブスクリプションモードは、サブスクライバとパブリケーションとの間にあるトピック/イベントチャネルを使用します.このイベントシステムは、サブスクライバとパブリケーションとの間の依存性を回避するために、コード定義アプリケーションに関連するイベントを許可します.

    参考資料

  • 《JavaScript设计模式》作者:Addy Osmani
  • 《JavaScript設計モードと開発実践》作者:曾探
  • 設計モード(3):オブザーバモードとパブリッシュ/サブスクリプションモードの違い