Edited at

【JSでデザインパターン】オブザーバ編

More than 3 years have passed since last update.


【JSでデザインパターン】オブザーバ編


はじめに

本記事は、『JavaScriptパターン』(O'Reilly)

の第七章を読んで、JavaScriptでいろいろなデザインパターンを試してみようと思い書いた記事です。なお、各記事が長くなりそうなので分割しました。


リンク一覧


Observer


Observerとは?

Observerは「傍観者、観察者」の意味ですね。DOM操作をするJavaScriptなどでは、たとえば、マウスオーバーやクリックといったブラウザイベントに対して、ポップアップを表示させる、アニメーションを発動させる、といった処理を定義できます。

DOM操作によるブラウザイベント関わらず、メッセージの送信、ログイン、そういったあらゆるイベントを含めたカスタムイベントを「観察」し、それらが発生したときにオブザーバに通知するようなパターン、それがObserverパターンです。なお、観察しているObserverに対して、観察されているオブジェクトはSubjectと呼ばれます。

SPAとかとなると、カスタムイベントを自分で数種類定義して、それに対する処理をコントロールする、といったことを普通にやりますので、デザインパターンとして理解しておくとそこらへんのコーディングがはかどるかと思います。


Mediator Patternとの違い

よくMediator Patternとの違いがわからない、結局ふたつの違いって何?という記事をよく見ます。自分も完全に理解できているとは言えませんので、ネット上の議論を参考に紹介します。


Observer pattern : the observed object manages its own list of observers (aka listeners) which must be notified when a certain event happens.

Mediator pattern : the observed object is not aware of the list of its observers, there is an external entity that makes the mapping between observed objects and observers.


対象となるオブジェクトの状態や変更を監視・観察しているという意味合いが強いのがObserverという印象です。単純なイベントリスナーとか。何か変更や状態のアップデートが起こったことを検知して、それに対してアクションを付与してあげる。試合の審判とかジャッジみたいですね。

一方Mediatorパターンでは、オブジェクトの状態や変更が起こったことを検知するというところまでは同じですが、それに対してMediatorパターンのコアメソッド自身が何かしらアクションを返してあげるというよりは、 妥当なメソッドに引き渡してあげる イメージです。だからこそ「Mediator(仲介者、まとめ役、橋渡し役)」なのでしょうね。交差点の真ん中で交通整備をしている警察官のイメージです。


参考リンク


使用例1: クリックイベントのためのObserverパターン


ObserverとSubject

簡単にいうと、Observerパターンには


  • Observer : イベントの発生を受けて状態を更新する

  • Subject : Observerの登録・削除・通知を行う

から基本的には構成されています。もっと詳しく言うと、


  • Observer


    • Subjectに変更があったときのメソッドを複数持つ


      • add/removeとか

      • publish/unpublishとか

      • subscribe/unsubscribeとか





  • Subject


    • Observerのリストを保持する

    • Observerの削除を行う

    • Observerに通知を送る



といった感じでしょうか。では、具体例で見て行きましょう。まずは最初にadd()メソッドやremoveメソッドを持つSubjectを定義します。


1. ObserversListの定義


// ObserversList
function ObserversList() {
this.handlers = []; // Observers
}

ObserversList.prototype = {
add : function(obj) {
this.handlers.push(obj);
},
remove : function(obj) {
for ( var i = 0, len = this.handlers.length; i < len; i++ ) {
if (this.handlers[i] === obj) {
this.handlers.splice(i, 1);
return true;
}
}
return false;
},
notify : function() {
var args = Array.prototype.slice.call(arguments, 0);
for (var i = 0, len = this.handlers.length; i < len; i++ ) {
this.handlers[i].update.apply(null, args);
}
}
}


2. Subject/Observerの定義

続いて、SubjectとObserverを定義していきましょう。


function Subject() {
var observersList = new ObserversList();

this.addObserver = function addObserver( obs ) {
observersList.add( obs );
};

this.removeObserver = function removeObserver( obs ) {
observersList.remove( obs );
};

this.fetchData = function fetchData() {
// fake data
var data = {
apple : "red",
strawberry : "red",
banana : "yellow"
};

observersList.notify( data );
}
}

// Observers

var DataUpdated = {
update : function() {
console.log("DataUpdated");
}
};

var DataPublished = {
update : function() {
console.log("DataPublished");
}
};


3. 動作確認

最後に動作確認をしてみましょう。


var subject = new Subject();

subject.addObserver(DataUpdated);
subject.fetchData(); // returns "DataUpdated"

subject.addObserver(DataPublished);
subject.fetchData(); // returns "DataUpdated" and "DataPublished"

subject.removeObserver(DataUpdated);
subject.fetchData(); // returns "DataPublished"


使用例2: 汎用的メソッド

本家本元のAddy Osmaniさんの記事では、より高度でかつ汎用的なサンプルコードを紹介しています。


ObserverList

まずはObserverを格納するための配列と、いくつかの汎用的メソッドです。



  • add() : 配列にオブジェクトを追加


  • count() : 配列の長さを返す


  • get() : 渡された順番に位置する配列要素を返す


  • indexOf() : 渡されたオブジェクトの配列の順番を返す


  • removeAt() : 渡された順番に位置する配列要素を削除する


// オブザーバを格納する配列を準備します
function ObserverList() {
this.observerList = [];
}

ObserverList.prototype = {
add : function(obj){
return this.observerList.push(obj);
},

count : function(){
return this.observerList.length;
},

get : function(index){
if (index > -1 && index < this.observerList.length){
return this.observerList[index];
}
},

indexOf : function(obj, startIndex){
var i = startIndex;

while( i < this.observerList.length ){
if (this.observerList[i] === obj){
return i;
}
i++;
}
return -1;
},

removeAt : function(index){
this.observerList.splice(index, 1);
}
}


Subject

続いて、オブザーバの追加・削除などを担当するSubjectです。


function Subject() {
this.observers = new ObserverList();
}

Subject.prototype = {

addObserver : function(observer){
this.observers.add( observer );
},

removeObserver : function(observer){
this.observers.removeAt(this.observers.indexOf(observer));
},

notify : function(context){
var observerCount = this.observers.count();
for(var i = 0; i < observerCount; i++){
this.observers.update(context);
}
}
}


Observer

最後に、オブザーバーを追加してみます。


function Observer(){
this.update = function(){
// ...
console.log("Observer.update()");
};

this.publish = function(){
// ...
console.log("Observer.publish()");
};
}


動作確認

最後に動作確認をしてみましょう。


var subject = new Subject();

subject.addObserver(ObserverUpdate);
subject.notify();

subject.addObserver(ObserverPublish);
subject.notify();

subject.removeObserver(ObserverUpdate);
subject.notify();


参考書籍