在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 觀察者模式
中介者模式
MVVM
亨元模式
設(shè)計模式分類概覽表
ES Harmony
組合模式
CommonJS
jQuery 插件的設(shè)計模式
外觀模式
觀察者模式
建造者模式
構(gòu)造器模式
外觀模式
簡介
AMD
原型模式
設(shè)計模式的分類
觀察者模式
命名空間模式
代理模式
編寫設(shè)計模式
適配器模式
反模式
什么是設(shè)計模式
模塊化模式
MVC
Mixin 模式
裝飾模式
設(shè)計模式的結(jié)構(gòu)
單例模式
迭代器模式
命令模式
工廠模式
MVP
暴露模塊模式
惰性初始模式

觀察者模式

觀察者模式是這樣一種設(shè)計模式。一個被稱作被觀察者的對象,維護(hù)一組被稱為觀察者的對象,這些對象依賴于被觀察者,被觀察者自動將自身的狀態(tài)的任何變化通知給它們。

當(dāng)一個被觀察者需要將一些變化通知給觀察者的時候,它將采用廣播的方式,這條廣播可能包含特定于這條通知的一些數(shù)據(jù)。

當(dāng)特定的觀察者不再需要接受來自于它所注冊的被觀察者的通知的時候,被觀察者可以將其從所維護(hù)的組中刪除。 在這里提及一下設(shè)計模式現(xiàn)有的定義很有必要。這個定義是與所使用的語言無關(guān)的。通過這個定義,最終我們可以更深層次地了解到設(shè)計模式如何使用以及其優(yōu)勢。在四人幫的《設(shè)計模式:可重用的面向?qū)ο筌浖脑亍愤@本書中,是這樣定義觀察者模式的:

一個或者更多的觀察者對一個被觀察者的狀態(tài)感興趣,將自身的這種興趣通過附著自身的方式注冊在被觀察者身上。當(dāng)被觀察者發(fā)生變化,而這種便可也是觀察者所關(guān)心的,就會產(chǎn)生一個通知,這個通知將會被送出去,最后將會調(diào)用每個觀察者的更新方法。當(dāng)觀察者不在對被觀察者的狀態(tài)感興趣的時候,它們只需要簡單的將自身剝離即可。

我們現(xiàn)在可以通過實(shí)現(xiàn)一個觀察者模式來進(jìn)一步擴(kuò)展我們剛才所學(xué)到的東西。這個實(shí)現(xiàn)包含一下組件:

  • 被觀察者:維護(hù)一組觀察者, 提供用于增加和移除觀察者的方法。
  • 觀察者:提供一個更新接口,用于當(dāng)被觀察者狀態(tài)變化時,得到通知。
  • 具體的被觀察者:狀態(tài)變化時廣播通知給觀察者,保持具體的觀察者的信息。
  • 具體的觀察者:保持一個指向具體被觀察者的引用,實(shí)現(xiàn)一個更新接口,用于觀察,以便保證自身狀態(tài)總是和被觀察者狀態(tài)一致的。

首先,讓我們對被觀察者可能有的一組依賴其的觀察者進(jìn)行建模:

function ObserverList(){
  this.observerList = [];
}

ObserverList.prototype.Add = function( obj ){
  return this.observerList.push( obj );
};

ObserverList.prototype.Empty = function(){
  this.observerList = [];
};

ObserverList.prototype.Count = function(){
  return this.observerList.length;
};

ObserverList.prototype.Get = function( index ){
  if( index > -1 && index < this.observerList.length ){
    return this.observerList[ index ];
  }
};

ObserverList.prototype.Insert = function( obj, index ){
  var pointer = -1;

  if( index === 0 ){
    this.observerList.unshift( obj );
    pointer = index;
  }else if( index === this.observerList.length ){
    this.observerList.push( obj );
    pointer = index;
  }

  return pointer;
};

ObserverList.prototype.IndexOf = function( obj, startIndex ){
  var i = startIndex, pointer = -1;

  while( i < this.observerList.length ){
    if( this.observerList[i] === obj ){
      pointer = i;
    }
    i++;
  }

  return pointer;
};

ObserverList.prototype.RemoveAt = function( index ){
  if( index === 0 ){
    this.observerList.shift();
  }else if( index === this.observerList.length -1 ){
    this.observerList.pop();
  }
};

// Extend an object with an extension
function extend( extension, obj ){
  for ( var key in extension ){
    obj[key] = extension[key];
  }
}

接著,我們對被觀察者以及其增加,刪除,通知在觀察者列表中的觀察者的能力進(jìn)行建模:

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 函數(shù)之后會被具體的行為覆蓋。

// The Observer
function Observer(){
  this.Update = function(){
    // ...
  };
}

在我們的樣例應(yīng)用里面,我們使用上面的觀察者組件,現(xiàn)在我們定義:

  • 一個按鈕,這個按鈕用于增加新的充當(dāng)觀察者的選擇框到頁面上
  • 一個控制用的選擇框 , 充當(dāng)一個被觀察者,通知其它選擇框是否應(yīng)該被選中
  • 一個容器,用于放置新的選擇框

我們接著定義具體被觀察者和具體觀察者,用于給頁面增加新的觀察者,以及實(shí)現(xiàn)更新接口。通過查看下面的內(nèi)聯(lián)的注釋,搞清楚在我們樣例中的這些組件是如何工作的。

HTML

<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>

Sample script

// 我們DOM 元素的引用

var controlCheckbox = document.getElementById( "mainCheckbox" ),
  addBtn = document.getElementById( "addNewObserver" ),
  container = document.getElementById( "observersContainer" );

// 具體的被觀察者

//Subject 類擴(kuò)展controlCheckbox 類
extend( new Subject(), controlCheckbox );

//點(diǎn)擊checkbox 將會觸發(fā)對觀察者的通知
controlCheckbox["onclick"] = new Function( "controlCheckbox.Notify(controlCheckbox.checked)" );

addBtn["onclick"] = AddNewObserver;

// 具體的觀察者

function AddNewObserver(){

  //建立一個新的用于增加的checkbox
  var check  = document.createElement( "input" );
  check.type = "checkbox";

  // 使用Observer 類擴(kuò)展checkbox
  extend( new Observer(), check );

  // 使用定制的Update函數(shù)重載
  check.Update = function( value ){
    this.checked = value;
  };

  // 增加新的觀察者到我們主要的被觀察者的觀察者列表中
  controlCheckbox.AddObserver( check );

  // 將元素添加到容器的最后
  container.appendChild( check );
}

在這個例子里面,我們看到了如何實(shí)現(xiàn)和配置觀察者模式,了解了被觀察者,觀察者,具體被觀察者,具體觀察者的概念。

觀察者模式和發(fā)布/訂閱模式的不同

觀察者模式確實(shí)很有用,但是在javascript時間里面,通常我們使用一種叫做發(fā)布/訂閱模式的變體來實(shí)現(xiàn)觀察者模式。這兩種模式很相似,但是也有一些值得注意的不同。

觀察者模式要求想要接受相關(guān)通知的觀察者必須到發(fā)起這個事件的被觀察者上注冊這個事件。

發(fā)布/訂閱模式使用一個主題/事件頻道,這個頻道處于想要獲取通知的訂閱者和發(fā)起事件的發(fā)布者之間。這個事件系統(tǒng)允許代碼定義應(yīng)用相關(guān)的事件,這個事件可以傳遞特殊的參數(shù),參數(shù)中包含有訂閱者所需要的值。這種想法是為了避免訂閱者和發(fā)布者之間的依賴性。

這種和觀察者模式之間的不同,使訂閱者可以實(shí)現(xiàn)一個合適的事件處理函數(shù),用于注冊和接受由發(fā)布者廣播的相關(guān)通知。

這里給出一個關(guān)于如何使用發(fā)布者/訂閱者模式的例子,這個例子中完整地實(shí)現(xiàn)了功能強(qiáng)大的publish(), subscribe() 和 unsubscribe()。

// 一個非常簡單的郵件處理器

// 接受的消息的計數(shù)器
var mailCounter = 0;

// 初始化一個訂閱者,這個訂閱者監(jiān)聽名叫"inbox/newMessage" 的頻道

// 渲染新消息的粗略信息
var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) {

  // 日志記錄主題,用于調(diào)試
  console.log( "A new message was received: ", topic );

  // 使用來自于被觀察者的數(shù)據(jù),用于給用戶展示一個消息的粗略信息
  $( ".messageSender" ).html( data.sender );
  $( ".messagePreview" ).html( data.body );

});

// 這是另外一個訂閱者,使用相同的數(shù)據(jù)執(zhí)行不同的任務(wù)

// 更細(xì)計數(shù)器,顯示當(dāng)前來自于發(fā)布者的新信息的數(shù)量
var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) {

  $('.newMessageCounter').html( mailCounter++ );

});

publish( "inbox/newMessage", [{
  sender:"hello@google.com",
  body: "Hey there! How are you doing today?"
}]);

// 在之后,我們可以讓我們的訂閱者通過下面的方式取消訂閱來自于新主題的通知
// unsubscribe( subscriber1,  );
// unsubscribe( subscriber2 );

這個例子的更廣的意義是對松耦合的原則的一種推崇。不是一個對象直接調(diào)用另外一個對象的方法,而是通過訂閱另外一個對象的一個特定的任務(wù)或者活動,從而在這個任務(wù)或者活動出現(xiàn)的時候的得到通知。

優(yōu)勢

觀察者和發(fā)布/訂閱模式鼓勵人們認(rèn)真考慮應(yīng)用不同部分之間的關(guān)系,同時幫助我們找出這樣的層,該層中包含有直接的關(guān)系,這些關(guān)系可以通過一些列的觀察者和被觀察者來替換掉。這中方式可以有效地將一個應(yīng)用程序切割成小塊,這些小塊耦合度低,從而改善代碼的管理,以及用于潛在的代碼復(fù)用。

使用觀察者模式更深層次的動機(jī)是,當(dāng)我們需要維護(hù)相關(guān)對象的一致性的時候,我們可以避免對象之間的緊密耦合。例如,一個對象可以通知另外一個對象,而不需要知道這個對象的信息。

兩種模式下,觀察者和被觀察者之間都可以存在動態(tài)關(guān)系。這提供很好的靈活性,而當(dāng)我們的應(yīng)用中不同的部分之間緊密耦合的時候,是很難實(shí)現(xiàn)這種靈活性的。

盡管這些模式并不是萬能的靈丹妙藥,這些模式仍然是作為最好的設(shè)計松耦合系統(tǒng)的工具之一,因此在任何的JavaScript 開發(fā)者的工具箱里面,都應(yīng)該有這樣一個重要的工具。

缺點(diǎn)

事實(shí)上,這些模式的一些問題實(shí)際上正是來自于它們所帶來的一些好處。在發(fā)布/訂閱模式中,將發(fā)布者共訂閱者上解耦,將會在一些情況下,導(dǎo)致很難確保我們應(yīng)用中的特定部分按照我們預(yù)期的那樣正常工作。

例如,發(fā)布者可以假設(shè)有一個或者多個訂閱者正在監(jiān)聽它們。比如我們基于這樣的假設(shè),在某些應(yīng)用處理過程中來記錄或者輸出錯誤日志。如果訂閱者執(zhí)行日志功能崩潰了(或者因?yàn)槟承┰虿荒苷9ぷ鳎?,因?yàn)橄到y(tǒng)本身的解耦本質(zhì),發(fā)布者沒有辦法感知到這些事情。

另外一個這種模式的缺點(diǎn)是,訂閱者對彼此之間存在沒有感知,對切換發(fā)布者的代價無從得知。因?yàn)橛嗛喺吆桶l(fā)布者之間的動態(tài)關(guān)系,更新依賴也很能去追蹤。

發(fā)布/訂閱實(shí)現(xiàn)

發(fā)布/訂閱在JavaScript的生態(tài)系統(tǒng)中非常合適,主要是因?yàn)樽鳛楹诵牡腅CMAScript 實(shí)現(xiàn)是事件驅(qū)動的。尤其是在瀏覽器環(huán)境下更是如此,因?yàn)镈OM使用事件作為其主要的用于腳本的交互API。

也就是說,無論是ECMAScript 還是DOM都沒有在實(shí)現(xiàn)代碼中提供核心對象或者方法用于創(chuàng)建定制的事件系統(tǒng)(DOM3 的CustomEvent是一個例外,這個事件綁定在DOM上,因此通常用處不大)。

幸運(yùn)的是,流行的JavaScript庫例如dojo, jQuery(定制事件)以及YUI已經(jīng)有相關(guān)的工具,可以幫助我們方便的實(shí)現(xiàn)一個發(fā)布/訂閱者系統(tǒng)。下面我們看一些例子。

// 發(fā)布

// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]);
$( el ).trigger( "/login", [{username:"test", userData:"test"}] );

// Dojo: dojo.publish("channel", [arg1, arg2, arg3] );
dojo.publish( "/login", [{username:"test", userData:"test"}] );

// YUI: el.publish("channel", [arg1, arg2, arg3]);
el.publish( "/login", {username:"test", userData:"test"} );

// 訂閱

// jQuery: $(obj).on( "channel", [data], fn );
$( el ).on( "/login", function( event ){...} );

// Dojo: dojo.subscribe( "channel", fn);
var handle = dojo.subscribe( "/login", function(data){..} );

// YUI: el.on("channel", handler);
el.on( "/login", function( data ){...} );

// 取消訂閱

// jQuery: $(obj).off( "channel" );
$( el ).off( "/login" );

// Dojo: dojo.unsubscribe( handle );
dojo.unsubscribe( handle );

// YUI: el.detach("channel");
el.detach( "/login" );

對于想要在vanilla Javascript(或者其它庫)中使用發(fā)布/訂閱模式的人來講, AmplifyJS 包含了一個干凈的,庫無關(guān)的實(shí)現(xiàn),可以和任何庫或者工具箱一起使用。Radio.js, PubSubJS 或者 Pure JS PubSub 來自于 Peter Higgins 都有類似的替代品值得研究。

尤其對于jQuery 開發(fā)者來講,他們擁有很多其它的選擇,可以選擇大量的良好實(shí)現(xiàn)的代碼,從Peter Higgins 的jQuery插件到Ben Alman 在GitHub 上的(優(yōu)化的)發(fā)布/訂閱 jQuery gist。下面給出了這些代碼的鏈接。

從上面我們可以看到在javascript中有這么多種觀察者模式的實(shí)現(xiàn),讓我們看一下最小的一個版本的發(fā)布/訂閱模式實(shí)現(xiàn),這個實(shí)現(xiàn)我放在github 上,叫做pubsubz。這個實(shí)現(xiàn)展示了發(fā)布,訂閱的核心概念,以及如何取消訂閱。

我之所以選擇這個代碼作為我們例子的基礎(chǔ),是因?yàn)檫@個代碼緊密貼合了方法簽名和實(shí)現(xiàn)方式,這種實(shí)現(xiàn)方式正是我想看到的javascript版本的經(jīng)典的觀察者模式所應(yīng)該有的樣子。

發(fā)布/訂閱實(shí)例

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 ));

示例:使用我們的實(shí)現(xiàn)

我們現(xiàn)在可以使用發(fā)布實(shí)例和訂閱感興趣的事件,例如:

// 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: "hello@google.com",
  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?" );

例如:用戶界面通知

接下來,讓我們想象一下,我們有一個Web應(yīng)用程序,負(fù)責(zé)顯示實(shí)時股票信息。

應(yīng)用程序可能有一個表格顯示股票統(tǒng)計數(shù)據(jù)和一個計數(shù)器顯示的最后更新點(diǎn)。當(dāng)數(shù)據(jù)模型發(fā)生變化時,應(yīng)用程序?qū)⑿枰卤砀窈陀嫈?shù)器。在這種情況下,我們的主題(這將發(fā)布主題/通知)是數(shù)據(jù)模型以及我們的訂閱者是表格和計數(shù)器。

當(dāng)我們的訂閱者收到通知:該模型本身已經(jīng)改變,他們自己可以進(jìn)行相應(yīng)的更新。

在我們的實(shí)現(xiàn)中,如果發(fā)現(xiàn)新的股票信息是可用的,我們的訂閱者將收聽到的主題“新數(shù)據(jù)可用”。如果一個新的通知發(fā)布到該主題,那將觸發(fā)表格去添加一個包含此信息的新行。它也將更新最后更新計數(shù)器,記錄最后一次添加的數(shù)據(jù)

// Return the current local time to be used in our UI later
getCurrentTime = function (){

   var date = new Date(),
         m = date.getMonth() + 1,
         d = date.getDate(),
         y = date.getFullYear(),
         t = date.toLocaleTimeString().toLowerCase();

        return (m + "/" + d + "/" + y + " " + t);
};

// Add a new row of data to our fictional grid component
function addGridRow( data ) {

   // ui.grid.addRow( data );
   console.log( "updated grid component with:" + data );

}

// Update our fictional grid to show the time it was last
// updated
function updateCounter( data ) {

   // ui.grid.updateLastChanged( getCurrentTime() );  
   console.log( "data last updated at: " + getCurrentTime() + " with " + data);

}

// Update the grid using the data passed to our subscribers
gridUpdate = function( topic, data ){

  if ( data !== "undefined" ) {
     addGridRow( data );
     updateCounter( data );
   }

};

// Create a subscription to the newDataAvailable topic
var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate );

// The following represents updates to our data layer. This could be
// powered by ajax requests which broadcast that new data is available
// to the rest of the application.

// Publish changes to the gridUpdated topic representing new entries
pubsub.publish( "newDataAvailable", {
  summary: "Apple made $5 billion",
  identifier: "APPL",
  stockPrice: 570.91
});

pubsub.publish( "newDataAvailable", {
  summary: "Microsoft made $20 million",
  identifier: "MSFT",
  stockPrice: 30.85
});

樣例:在下面這個電影評分的例子里面,我們使用Ben Alman的發(fā)布/訂閱實(shí)現(xiàn)來解耦應(yīng)用程序。我們使用Ben Alman的jQuery實(shí)現(xiàn),來展示如何解耦用戶界面。請注意,我們?nèi)绾巫龅教峤灰粋€評分,來產(chǎn)生一個發(fā)布信息,這個信息表明了當(dāng)前新的用戶和評分?jǐn)?shù)據(jù)可用。

剩余的工作留給訂閱者,由訂閱者來代理這些主題中的數(shù)據(jù)發(fā)生的變化。在我們的例子中,我們將新的數(shù)據(jù)壓入到現(xiàn)存的數(shù)組中,接著使用Underscore庫的template()方法來渲染模板。

HTML/模板

<script id="userTemplate" type="text/html">
   <li><%= name %></li>
</script>

<script id="ratingsTemplate" type="text/html">
   <li><strong><%= title %></strong> was rated <%= rating %>/5</li>
</script>

<div id="container">

   <div class="sampleForm">
       <p>
           <label for="twitter_handle">Twitter handle:</label>
           <input type="text" id="twitter_handle" />
       </p>
       <p>
           <label for="movie_seen">Name a movie you've seen this year:</label>
           <input type="text" id="movie_seen" />
       </p>
       <p>

           <label for="movie_rating">Rate the movie you saw:</label>
           <select id="movie_rating">
                 <option value="1">1</option>
                  <option value="2">2</option>
                  <option value="3">3</option>
                  <option value="4">4</option>
                  <option value="5" selected>5</option>

          </select>
        </p>
        <p>

            <button id="add">Submit rating</button>
        </p>
    </div>

    <div class="summaryTable">
        <div id="users"><h3>Recent users</h3></div>
        <div id="ratings"><h3>Recent movies rated</h3></div>
    </div>

 </div>

JavaScript

;(function( $ ) {

  // Pre-compile templates and "cache" them using closure
  var
    userTemplate = _.template($( "#userTemplate" ).html()),
    ratingsTemplate = _.template($( "#ratingsTemplate" ).html());

  // Subscribe to the new user topic, which adds a user
  // to a list of users who have submitted reviews
  $.subscribe( "/new/user", function( e, data ){

    if( data ){

      $('#users').append( userTemplate( data ));

    }

  });

  // Subscribe to the new rating topic. This is composed of a title and
  // rating. New ratings are appended to a running list of added user
  // ratings.
  $.subscribe( "/new/rating", function( e, data ){

    var compiledTemplate;

    if( data ){

      $( "#ratings" ).append( ratingsTemplate( data );

    }

  });

  // Handler for adding a new user
  $("#add").on("click", function( e ) {

    e.preventDefault();

    var strUser = $("#twitter_handle").val(),
       strMovie = $("#movie_seen").val(),
       strRating = $("#movie_rating").val();

    // Inform the application a new user is available
    $.publish( "/new/user",  { name: strUser } );

    // Inform the app a new rating is available
    $.publish( "/new/rating",  { title: strMovie, rating: strRating} );

    });

})( jQuery );

樣例:解耦一個基于Ajax的jQuery應(yīng)用。

在我們最后的例子中,我們將從實(shí)用的角度來看一下如何在開發(fā)早起使用發(fā)布/訂閱模式來解耦代碼,這樣可以幫助我們避免之后痛苦的重構(gòu)過程。

在Ajax重度依賴的應(yīng)用里面,我們常會見到這種情況,當(dāng)我們收到一個請求的響應(yīng)之后,我們希望能夠完成不僅僅一個特定的操作。我們可以簡單的將所有請求后的邏輯加入到成功的回調(diào)函數(shù)里面,但是這樣做有一些問題。

高度耦合的應(yīng)用優(yōu)勢會增加重用功能的代價,因?yàn)楦叨锐詈显黾恿藘?nèi)部函數(shù)/代碼的依賴性。這意味著如果我們只是希望獲取一次性獲取結(jié)果集,可以將請求后 的邏輯代碼 硬編碼在回調(diào)函數(shù)里面,這種方式可以正常工作,但是當(dāng)我們想要對相同的數(shù)據(jù)源(不同的最終行為)做更多的Ajax調(diào)用的時候,這種方式就不適合了,我們必須要多次重寫部分代碼。與其回溯調(diào)用相同數(shù)據(jù)源的每一層,然后在將它們泛化,不如一開始就使用發(fā)布/訂閱模式來節(jié)約時間。

使用觀察者,我們可以簡單的將整個應(yīng)用范圍的通知進(jìn)行隔離,針對不同的事件,我們可以把這種隔離做到我們想要的粒度上,如果使用其它模式,則可能不會有這么優(yōu)雅的實(shí)現(xiàn)。

注意我們下面的例子中,當(dāng)用戶表明他們想要做一次搜索查詢的時候,一個話題通知就會生成,而當(dāng)請求返回,并且實(shí)際的數(shù)據(jù)可用的時候,又會生成另外一個通知。而如何使用這些事件(或者返回的數(shù)據(jù)),都是由訂閱者自己決定的。這樣做的好處是,如果我們想要,我們可以有10個不同的訂閱者,以不同的方式使用返回的數(shù)據(jù),而對于Ajax層來講,它不會關(guān)心你如何處理數(shù)據(jù)。它唯一的責(zé)任就是請求和返回數(shù)據(jù),接著將數(shù)據(jù)發(fā)送給所有想要使用數(shù)據(jù)的地方。這種相關(guān)性上的隔離可以是我們整個代碼設(shè)計更為清晰。

HTML/Templates

<form id="flickrSearch">

   <input type="text" name="tag" id="query"/>

   <input type="submit" name="submit" value="submit"/>

</form>

<div id="lastQuery"></div>

<div id="searchResults"></div>

<script id="resultTemplate" type="text/html">
    <% _.each(items, function( item ){  %>
            <li><p><img src="<%= item.media.m %>"/></p></li>
    <% });%>
</script>

JavaScript

;(function( $ ) {

   // Pre-compile template and "cache" it using closure
   var resultTemplate = _.template($( "#resultTemplate" ).html());

   // Subscribe to the new search tags topic
   $.subscribe( "/search/tags" , function( tags ) {
       $( "#searchResults" )
                .html("
<p>
    Searched for:<strong>" + tags + "</strong>
</p>
");
   });

   // Subscribe to the new results topic
   $.subscribe( "/search/resultSet" , function( results ){

       $( "#searchResults" ).append(resultTemplate( results ));

   });

   // Submit a search query and publish tags on the /search/tags topic
   $( "#flickrSearch" ).submit( function( e ) {

       e.preventDefault();
       var tags = $(this).find( "#query").val();

       if ( !tags ){
        return;
       }

       $.publish( "/search/tags" , [ $.trim(tags) ]);

   });

   // Subscribe to new tags being published and perform
   // a search query using them. Once data has returned
   // publish this data for the rest of the application
   // to consume

   $.subscribe("/search/tags", function( tags ) {

       $.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?" ,{
              tags: tags,
              tagmode: "any",
              format: "json"
            },

          function( data ){

              if( !data.items.length ) {
                return;
              }

              $.publish( "/search/resultSet" , data.items  );
       });

   });

})();

觀察者模式在應(yīng)用設(shè)計中,解耦一系列不同的場景上非常有用,如果你沒有用過它,我推薦你嘗試一下今天提到的之前寫到的某個實(shí)現(xiàn)。這個模式是一個易于學(xué)習(xí)的模式,同時也是一個威力巨大的模式。

上一篇:反模式下一篇:迭代器模式