事件的订阅发布机制以及在前端中的应用
引用自https://en.wikipedia.org/wiki/Publish–subscribe_pattern
简介
在软件结构中,发布订阅是一种消息模式(一种面向网络结构的模式,描述了两种不同部分的消息怎么去互相连接和沟通),其中包括非直接传递信息的publisher叫publish,和接收信息的subscriber叫subscribe。subscriber把不同的消息进行分类,同时不需要知道subscriber是谁。subscriber订阅感兴趣的分类,并只接收订阅的分类中信息,同时也不需要知道publisher是谁。
发布订阅机制通常和消息队列机制一样,也是一种典型的大型的面相信息中间件系统的一部分。
消息过滤
在订阅发布模型中,subscriber只接收所有发布的信息中的一部分。选择接收的信息的过程叫过滤。有两种过滤机制:
基于主题:
publisher发布不同主题的消息,subscriber接收选择已订阅的主题的消息。不同subscriber订阅相同主题的话接收的信息相同。publisher定义不同subscriber可以订阅的信息的类别。
基于内容:
只有当发送的消息的属性或者内容符合subscriber制定的规则,消息才会发送给subscribes。
一些系统支持两种过滤机制的混合使用,即publisher发布一个消息到一个主题,同时subscriber制定了基于内容的限制的订阅在这个主题上。
应用
在很多订阅发布系统中,publisher发布信息到一个信息中介或者是事件公交车,同时subscriber注册订阅信息到这个信息中介,同时信息中介担当着过滤的作用。信息中介一般扮演着暂存和继续发送从publisher按照路线发送到subscriber的消息的角色。另外,信息中介可能会在按路线运送消息前在消息队列中对消息的优先级进行处理。
subscriber可能会注册特定的消息在构建时,初始化时或者运行时。GUI系统在系统构建的时候就会进行用户点击等消息的订阅。一些框架和软件产品用XML在初始化注册订阅信息。许多复杂的别的系统则在运行时进行订阅处理 ,例如RSS。
优点
1.低耦合
publisher和subscriber时低耦合的,他们可以继续运行而不用管对方的状态。在传统的客服端-服务器范例中,服务器不运行,客户端就不能发送消息到服务器。客户端不运行,服务器就接收不到消息。
2.可扩展型
通过平行的运行机制,消息缓存机制,基于一定规则的消息发送机,相比通常的客户端-服务端模式,订阅发布模式有更好的扩展性。但是在高耦合大容量的企业级环境中,不适合订阅发布机制。
缺点
低耦合
为什么这么说呢?publisher的消息数据结构必须定义的很合理,有相当的伸缩性。为了更改要发布的消息数据结构,publisher可能要知道所有的subscriber,同时也要修改他们或者与旧版本保持兼容。这使得重构publisher变的很困难。因为随着时间推移可能会更改需求,publisher数据结构的不变性会成为负担。
However, this is a common issue with any client/server architecture and is best served by versioning content payloads or topics and/or changing URL end points for backward compatibility.
我用到这个设计模式是为了解决在ajax回调中,进行组件化的问题。即在需要使用ajax数据的组件中进行订阅事件,在ajax回调中,或者model中进行发布事件,从而降低耦合,同时避免回调地狱。
具体的实现代码如下
/*
* jQuery global custom event plugin (gevent)
*
* Copyright (c) 2013 Michael S. Mikowski
* (mike[dot]mikowski[at]gmail[dotcom])
*
* Dual licensed under the MIT or GPL Version 2
* http://jquery.org/license
*
* Versions
* 0.1.5 - initial release
* 0.1.6 - enhanced publishEvent (publish) method pass
* a non-array variable as the second argument
* to a subscribed function (the first argument
* is always the event object).
* 0.1.7
* 0.1.8
* 0.1.9 - documentation changes
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global jQuery */
(function ( $ ) {
'use strict';
$.gevent = ( function () {
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
subscribeEvent, publishEvent, unsubscribeEvent,
$customSubMap = {}
;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN PUBLIC METHODS -------------------
// BEGIN public method /publishEvent/
// Example :
// $.gevent.publish(
// 'spa-model-msg-receive',
// [ { user : 'fred', msg : 'Hi gang' } ]
// );
// Purpose :
// Publish an event with an optional list of arguments
// which a subscribed handler will receive after the event object.
// Arguments (positional)
// * 0 ( event_name ) - The global event name
// * 2 ( data ) - Optional data to be passed as argument(s)
// to subscribed functions after the event
// object. Provide an array for multiple
// arguments.
// Throws : none
// Returns : none
//
publishEvent = function ( event_name, data ) {
var data_list;
if ( ! $customSubMap[ event_name ] ){ return false; }
if ( data ){
data_list = Array.isArray( data ) ? data : [ data ];
$customSubMap[ event_name ].trigger( event_name, data_list );
return true;
}
$customSubMap[ event_name ].trigger( event_name );
return true;
};
// END public method /publishEvent/
// BEGIN public method /subscribeEvent/
// Example :
// $.gevent.subscribe(
// $( '#msg' ),
// 'spa-msg-receive',
// onModelMsgReceive
// );
// Purpose :
// Subscribe a function to a published event on a jQuery collection
// Arguments (positional)
// * 0 ( $collection ) - The jQuery collection on which to bind event
// * 1 ( event_name ) - The global event name
// * 2 ( fn ) - The function to bound to the event on the collection
// Throws : none
// Returns : none
//
subscribeEvent = function ( $collection, event_name, fn ) {
$collection.on( event_name, fn );
if ( ! $customSubMap[ event_name ] ) {
$customSubMap[ event_name ] = $collection;
}
else {
$customSubMap[ event_name ]
= $customSubMap[ event_name ].add( $collection );
}
};
// END public method /subscribeEvent/
// BEGIN public method /unsubscribeEvent/
// Example :
// $.gevent.unsubscribe(
// $( '#msg' ),
// 'spa-model-msg-receive'
// );
// Purpose :
// Remove a binding for the named event on a provided collection
// Arguments (positional)
// * 0 ( $collection ) - The jQuery collection on which to bind event
// * 1 ( event_name ) - The global event name
// Throws : none
// Returns : none
//
unsubscribeEvent = function ( $collection, event_name ) {
if ( ! $customSubMap[ event_name ] ){ return false; }
$customSubMap[ event_name ]
= $customSubMap[ event_name ].not( $collection );
if ( $customSubMap[ event_name ].length === 0 ){
delete $customSubMap[ event_name ];
}
return true;
};
// END public method /unsubscribeEvent/
//------------------- END PUBLIC METHODS ---------------------
// return public methods
return {
publish : publishEvent,
subscribe : subscribeEvent,
unsubscribe : unsubscribeEvent
};
}());
}( jQuery ));
//使用时 model module中
get_room_list_info_m = function (data){
$.gevent.publish('spa-get_room_list_info',data);
}
//具体的业务代码 例如room_list module 中
// 订阅获取房间信息列表事件
$.gevent.subscribe(jqueryMap.$room_list, 'spa-get_room_list_info', setPage);