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

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

中介者模式

字典中中介者的定義是,一個中立方,在談判和沖突解決過程中起輔助作用。在我們的世界,一個中介者是一個行為設(shè)計模式,使我們可以導出統(tǒng)一的接口,這樣系統(tǒng)不同部分就可以彼此通信。

如果系統(tǒng)組件之間存在大量的直接關(guān)系,就可能是時候,使用一個中心的控制點,來讓不同的組件通過它來通信。中介者通過將組件之間顯式的直接的引用替換成通過中心點來交互的方式,來做到松耦合。這樣可以幫助我們解耦,和改善組件的重用性。

在現(xiàn)實世界中,類似的系統(tǒng)就是,飛行控制系統(tǒng)。一個航站塔(中介者)處理哪個飛機可以起飛,哪個可以著陸,因為所有的通信(監(jiān)聽的通知或者廣播的通知)都是飛機和控制塔之間進行的,而不是飛機和飛機之間進行的。一個中央集權(quán)的控制中心是這個系統(tǒng)成功的關(guān)鍵,也正是中介者在軟件設(shè)計領(lǐng)域中所扮演的角色。

從實現(xiàn)角度來講,中介者模式是觀察者模式中的共享被觀察者對象。在這個系統(tǒng)中的對象之間直接的發(fā)布/訂閱關(guān)系被犧牲掉了,取而代之的是維護一個通信的中心節(jié)點。

也可以認為是一種補充-用于應(yīng)用級別的通知,例如不同子系統(tǒng)之間的通信,子系統(tǒng)本身很復(fù)雜,可能需要使用發(fā)布/訂閱模式來做內(nèi)部組件之間的解耦。

另外一個類似的例子是DOM的事件冒泡機制,以及事件代理機制。如果系統(tǒng)中所有的訂閱者都是對文檔訂閱,而不是對獨立的節(jié)點訂閱,那么文檔就充當一個中介者的角色。DOM的這種做法,不是將事件綁定到獨立節(jié)點上,而是用一個更高級別的對象負責通知訂閱者關(guān)于交互事件的信息。

基礎(chǔ)的實現(xiàn)

中間人模式的一種簡單的實現(xiàn)可以在下面找到,publish()和subscribe()方法都被暴露出來使用:

var mediator = (function(){

    // Storage for topics that can be broadcast or listened to
    var topics = {};

    // Subscribe to a topic, supply a callback to be executed
    // when that topic is broadcast to
    var subscribe = function( topic, fn ){

        if ( !topics[topic] ){
          topics[topic] = [];
        }

        topics[topic].push( { context: this, callback: fn } );

        return this;
    };

    // Publish/broadcast an event to the rest of the application
    var publish = function( topic ){

        var args;

        if ( !topics[topic] ){
          return false;
        }

        args = Array.prototype.slice.call( arguments, 1 );
        for ( var i = 0, l = topics[topic].length; i < l; i++ ) {

            var subscription = topics[topic][i];
            subscription.callback.apply( subscription.context, args );
        }
        return this;
    };

    return {
        publish: publish,
        subscribe: subscribe,
        installTo: function( obj ){
            obj.subscribe = subscribe;
            obj.publish = publish;
        }
    };

}());

高級的實現(xiàn)

對于那些對更加高級實現(xiàn)感興趣的人,以走讀的方式看一看以下我對Jack Lawson優(yōu)秀的Mediator.js重寫的一個縮略版本.在其它方面的改進當中,為我們的中間人支持主題命名空間,用戶拆卸和一個更加穩(wěn)定的發(fā)布/訂閱系統(tǒng)。但是如果你想跳過這個走讀,你可以直接進入到下一個例子繼續(xù)閱讀。

得感謝Jack優(yōu)秀的代碼注釋對這部分內(nèi)容的協(xié)助。

首先,讓我們實現(xiàn)認購的概念,我們可以考慮一個中間人主題的注冊。

通過生成對象實體,我們稍后能夠簡單的更新認購,而不需要去取消注冊然后重新注冊它們.認購可以寫成一個使用被稱作一個選項對象或者一個上下文環(huán)境的函數(shù)

// Pass in a context to attach our Mediator to.
// By default this will be the window object
(function( root ){

  function guidGenerator() { /*..*/}

  // Our Subscriber constructor
  function Subscriber( fn, options, context ){

    if ( !(this instanceof Subscriber) ) {

      return new Subscriber( fn, context, options );

    }else{

      // guidGenerator() is a function that generates
      // GUIDs for instances of our Mediators Subscribers so
      // we can easily reference them later on. We're going
      // to skip its implementation for brevity

      this.id = guidGenerator();
      this.fn = fn;
      this.options = options;
      this.context = context;
      this.topic = null;

    }
  }
})();

在我們的中間人主題中包涵了一長串的回調(diào)和子主題,當中間人發(fā)布在我們中間人實體上被調(diào)用的時候被啟動.它也包含操作數(shù)據(jù)列表的方法

// Let's model the Topic.
// JavaScript lets us use a Function object as a
// conjunction of a prototype for use with the new
// object and a constructor function to be invoked.
function Topic( namespace ){

  if ( !(this instanceof Topic) ) {
    return new Topic( namespace );
  }else{

    this.namespace = namespace || "";
    this._callbacks = [];
    this._topics = [];
    this.stopped = false;

  }
}

// Define the prototype for our topic, including ways to
// add new subscribers or retrieve existing ones.
Topic.prototype = {

  // Add a new subscriber
  AddSubscriber: function( fn, options, context ){

    var callback = new Subscriber( fn, options, context );

    this._callbacks.push( callback );

    callback.topic = this;

    return callback;
  },
...

我們的主題實體被當做中間人調(diào)用的一個參數(shù)被傳遞.使用一個方便實用的calledStopPropagation()方法,回調(diào)就可以進一步被傳播開來:

StopPropagation: function(){
  this.stopped = true;
},

我們也能夠使得當提供一個GUID的標識符的時候檢索訂購用戶更加容易:

GetSubscriber: function( identifier ){

  for(var x = 0, y = this._callbacks.length; x < y; x++ ){
    if( this._callbacks[x].id == identifier || this._callbacks[x].fn == identifier ){
      return this._callbacks[x];
    }
  }

  for( var z in this._topics ){
    if( this._topics.hasOwnProperty( z ) ){
      var sub = this._topics[z].GetSubscriber( identifier );
      if( sub !== undefined ){
        return sub;
      }
    }
  }

},

接著,在我們需要它們的情況下,我們也能夠提供添加新主題,檢查現(xiàn)有的主題或者檢索主題的簡單方法:

AddTopic: function( topic ){
  this._topics[topic] = new Topic( (this.namespace ? this.namespace + ":" : "") + topic );
},

HasTopic: function( topic ){
  return this._topics.hasOwnProperty( topic );
},

ReturnTopic: function( topic ){
  return this._topics[topic];
},

如果我們覺得不再需要它們了,我們也可以明確的刪除這些訂購用戶.下面就是通過它的其子主題遞歸刪除訂購用戶的代碼:

RemoveSubscriber: function( identifier ){

  if( !identifier ){
    this._callbacks = [];

    for( var z in this._topics ){
      if( this._topics.hasOwnProperty(z) ){
        this._topics[z].RemoveSubscriber( identifier );
      }
    }
  }

  for( var y = 0, x = this._callbacks.length; y < x; y++ ) {
    if( this._callbacks[y].fn == identifier || this._callbacks[y].id == identifier ){
      this._callbacks[y].topic = null;
      this._callbacks.splice( y,1 );
      x--; y--;
    }
  }

},

接著我們通過遞歸子主題將發(fā)布任意參數(shù)的能夠包含到訂購服務(wù)對象中:

Publish: function( data ){

    for( var y = 0, x = this._callbacks.length; y < x; y++ ) {

        var callback = this._callbacks[y], l;
          callback.fn.apply( callback.context, data );

      l = this._callbacks.length;

      if( l < x ){
        y--;
        x = l;
      }
    }

    for( var x in this._topics ){
      if( !this.stopped ){
        if( this._topics.hasOwnProperty( x ) ){
          this._topics[x].Publish( data );
        }
      }
    }

    this.stopped = false;
  }
};

接著我們暴露我們將主要交互的調(diào)節(jié)實體.這里它是通過注冊的并且從主題中刪除的事件來實現(xiàn)的

function Mediator() {

  if ( !(this instanceof Mediator) ) {
    return new Mediator();
  }else{
    this._topics = new Topic( "" );
  }

};

想要更多先進的用例,我們可以看看調(diào)解支持的主題命名空間,下面這樣的asinbox:messages:new:read.GetTopic 返回基于一個命名空間的主題實體。

Mediator.prototype = {

  GetTopic: function( namespace ){
    var topic = this._topics,
        namespaceHierarchy = namespace.split( ":" );

    if( namespace === "" ){
      return topic;
    }

    if( namespaceHierarchy.length > 0 ){
      for( var i = 0, j = namespaceHierarchy.length; i < j; i++ ){

        if( !topic.HasTopic( namespaceHierarchy[i]) ){
          topic.AddTopic( namespaceHierarchy[i] );
        }

        topic = topic.ReturnTopic( namespaceHierarchy[i] );
      }
    }

    return topic;
  },

這一節(jié)我們定義了一個Mediator.Subscribe方法,它接受一個主題命名空間,一個將要被執(zhí)行的函數(shù),選項和又一個在訂閱中調(diào)用函數(shù)的上下文環(huán)境.這樣就創(chuàng)建了一個主題,如果這樣的一個主題存在的話

Subscribe: function( topiclName, fn, options, context ){
  var options = options || {},
      context = context || {},
      topic = this.GetTopic( topicName ),
      sub = topic.AddSubscriber( fn, options, context );

  return sub;
},

根據(jù)這一點,我們可以進一步定義能夠訪問特定訂閱用戶,或者將他們從主題中遞歸刪除的工具

// Returns a subscriber for a given subscriber id / named function and topic namespace

GetSubscriber: function( identifier, topic ){
  return this.GetTopic( topic || "" ).GetSubscriber( identifier );
},

// Remove a subscriber from a given topic namespace recursively based on
// a provided subscriber id or named function.

Remove: function( topicName, identifier ){
  this.GetTopic( topicName ).RemoveSubscriber( identifier );
},

我們主要的發(fā)布方式可以讓我們隨意發(fā)布數(shù)據(jù)到選定的主題命名空間,這可以在下面的代碼中看到。

主題可以被向下遞歸.例如,一條對inbox:message的post將發(fā)送到inbox:message:new和inbox:message:new:read.它將像接下來這樣被使用:Mediator.Publish( "inbox:messages:new", [args] );

Publish: function( topicName ){
    var args = Array.prototype.slice.call( arguments, 1),
        topic = this.GetTopic( topicName );

    args.push( topic );

    this.GetTopic( topicName ).Publish( args );
  }
};

最后,我們可以很容易的暴露我們的中間人,將它附著在傳遞到根中的對象上:

 root.Mediator = Mediator;
  Mediator.Topic = Topic;
  Mediator.Subscriber = Subscriber;

// Remember we can pass anything in here. I've passed inwindowto
// attach the Mediator to, but we can just as easily attach it to another
// object if desired.
})( window );

示例

無論是使用來自上面的實現(xiàn)(簡單的選項和更加先進的選項都是),我們能夠像下面這樣將一個簡單的聊天記錄系統(tǒng)整到一起:

HTML

<h1>Chat</h1>
<form id="chatForm">
    <label for="fromBox">Your Name:</label>
    <input id="fromBox" type="text"/>
    <br />
    <label for="toBox">Send to:</label>
    <input id="toBox" type="text"/>
    <br />
    <label for="chatBox">Message:</label>
    <input id="chatBox" type="text"/>
    <button type="submit">Chat</button>
</form>

<div id="chatResult"></div>

Javascript

$( "#chatForm" ).on( "submit", function(e) {
    e.preventDefault();

    // Collect the details of the chat from our UI
    var text = $( "#chatBox" ).val(),
        from = $( "#fromBox" ).val(),
        to = $( "#toBox" ).val();

    // Publish data from the chat to the newMessage topic
    mediator.publish( "newMessage" , { message: text, from: from, to: to } );
});

// Append new messages as they come through
function displayChat( data ) {
    var date = new Date(),
        msg = data.from + " said \"" + data.message + "\" to " + data.to;

    $( "#chatResult" )
        .prepend("
<p>
    " + msg + " (" + date.toLocaleTimeString() + ")
</p>
");
}

// Log messages
function logChat( data ) {
    if ( window.console ) {
        console.log( data );
    }
}

// Subscribe to new chat messages being submitted
// via the mediator
mediator.subscribe( "newMessage", displayChat );
mediator.subscribe( "newMessage", logChat );

// The following will however only work with the more advanced implementation:

function amITalkingToMyself( data ) {
    return data.from === data.to;
}

function iAmClearlyCrazy( data ) {
    $( "#chatResult" ).prepend("
<p>
    " + data.from + " is talking to himself.
</p>
");
}

 mediator.Subscribe( amITalkingToMyself, iAmClearlyCrazy );

優(yōu)點&缺點

中間人模式最大的好處就是,它節(jié)約了對象或者組件之間的通信信道,這些對象或者組件存在于從多對多到多對一的系統(tǒng)之中。由于解耦合水平的因素,添加新的發(fā)布或者訂閱者是相對容易的。

也許使用這個模式最大的缺點是它可以引入一個單點故障。在模塊之間放置一個中間人也可能會造成性能損失,因為它們經(jīng)常是間接地的進行通信的。由于松耦合的特性,僅僅盯著廣播很難去確認系統(tǒng)是如何做出反應(yīng)的。

這就是說,提醒我們自己解耦合的系統(tǒng)擁有許多其它的好處,是很有用的——如果我們的模塊互相之間直接的進行通信,對于模塊的改變(例如:另一個模塊拋出了異常)可以很容易的對我們系統(tǒng)的其它部分產(chǎn)生多米諾連鎖效應(yīng)。這個問題在解耦合的系統(tǒng)中很少需要被考慮到。

在一天結(jié)束的時候,緊耦合會導致各種頭痛,這僅僅只是另外一種可選的解決方案,但是如果得到正確實現(xiàn)的話也能夠工作得很好。

中間人VS觀察者

開發(fā)人員往往不知道中間人模式和觀察者模式之間的區(qū)別。不可否認,這兩種模式之間有一點點重疊,但讓我們回過頭來重新尋求GoF的一種解釋:

“在觀察者模式中,沒有封裝約束的單一對象”。取而代之,觀察者和主題必須合作來維護約束。通信的模式?jīng)Q定于觀察者和主題相互關(guān)聯(lián)的方式:一個單獨的主題經(jīng)常有許多的觀察者,而有時候一個主題的觀察者是另外一個觀察者的主題?!?/p>

中間人和觀察者都提倡松耦合,然而,中間人默認使用讓對象嚴格通過中間人進行通信的方式實現(xiàn)松耦合。觀察者模式則創(chuàng)建了觀察者對象,這些觀察者對象會發(fā)布觸發(fā)對象認購的感興趣的事件。

中間人VS門面

不久我們的描述就將涵蓋門面模式,但作為參考之用,一些開發(fā)者也想知道中間人和門面模式之間有哪些相似之處。它們都對模塊的功能進行抽象,但有一些細微的差別。

中間人模式讓模塊之間集中進行通信,它會被這些模塊明確的引用。門面模式卻只是為模塊或者系統(tǒng)定義一個更加簡單的接口,但不添加任何額外的功能。系統(tǒng)中其他的模塊并不直接意識到門面的概念,而可以被認為是單向的。

上一篇:MVP下一篇:外觀模式