1. Brief                            

  一直对Observer Pattern和Pub/Sub Pattern有所混淆,下面打算通过这两篇Blog来梳理这两种模式。若有纰漏请大家指正。

2. Role                            

Publisher:消息发布者,组装原始消息实体并触发消息传递的主体。

Subscriber:消息订阅者,接收消息实体并作出响应的主体。

Message Broker or Event Bus:消息发布者 与 消息订阅者 间的媒介,内含消息过滤和消息路由的功能,并可通过内置的消息队列(message queue)现实优先级处理。

     其中 过滤 功能又细分为topic-based和content-based两种类型

      topic-based就是为消息主题建立独立通道,订阅特定主题的订阅者将通过对应的通道接收消息;

content-based就是以消息内容为处理源,订阅者仅接收消息内容与目标关键词匹配的消息。

此外还可以采用hybird=topic-based和content-based的方式。

3. Advantages                        

 1. Loose coupling

  由于引入Message Broker来处理消息过滤和路由等功能,而Subscriber又是通过Messsage Broker来订阅消息,从而实现Publisher和Subscriber的低耦合。这种低耦合体现在两个方面。

空间:Publisher和Subscriber可运行在两个不同的进程,甚至是机器上;

时间:Publisher和Subscriber不必同时运行,通过Message Broker作为消息中转站暂存消息(Store and forward)并实现消息异步处理。

 2. Scalability

由于Publisher和Subscriber在空间和时间上松耦合,那么我们就可以通过增加进程/机器的方式来提高处理能力。

4. Disadvantages —— Semantic Coupling         

  "The most insidious kind of coupling occurs when one module makes use, not of some syntactic element of another module, but of some semantic knowledge of another module's inner workings" ——  chapter 5 of Code Complete

语义耦合(Semantic Coupling),是一种隐晦的耦合类型,导致代码重构、调试、修改复杂度急剧增加的主要原因。

类型如下:

1. 操作顺序耦合:使用一个对象,需要先调用Init(),之后才能调用DoAnything()。这种顺序耦合,即使在文档中remark也是极为不优雅的做法;

2. 全局参数传递:模块A修改了某个全局参数g_val,模块B读取该值。模块B必须知道模块A已经对该参数赋值;

3. 业务封装不够紧密:模块A向模块B传一个参数,模块B根据该参数选择对应的操作。模块A必须知道与业务相关的所有的操作类型。对于模块A,仅传递模块A自身可以理解的语义,或者通俗的概念作为参数,而不是被封装的业务相关的参数;

     4. 超越接口的数据类型约定:模块A向模块B传递一个接口的指针,模块B将其强制转换为派生类的指针。当模块B知道该接口的实际类型时,封装已经被破坏了。非相关模块只能对接口操作,而不应对接口之外的职责进行约定。

public interface Customer{}
public class VIP : Customer{
public void Serve(){}
} public static void main(String[] args){
Customer customer = new VIP();
Serve(customer);
}
public static void Serve(Customer customer){
VIP vip = (VIP)customer;
vip.Serve();
}

5. Simple implementation             

/**
* @class
* @private
* @description 帮助类
*/
class Utils{
/* @method 对用户输入的filter进行加工
* @static
* @package
* @param {DOMString|Function} origFilter - 用户输出的filter
* @returns {Function}
* @exception
*/
static wrapFilter(origFilter){
var type = typeof origFilter
if ('string' === type) return message => RegExp(origFilter, 'i').test(message.topic)
else if ('function' === type) return origFilter
throw Error('the type of argument(0) is wrong, which accepts string or function instance only!')
}
/* @method 添加消息到消息队列
* @static
* @package
* @param {Array.<Message>} mq - 消息队列
* @param {Message} message - 被添加的消息实例
* @param {Object.<{Number} GUIDofTimer, {Number} pause>} timer - 定时执行器
*/
static addMessage(mq, message, timer){
timer.pause =
if (!(timer.pause = mq.length)) return mq.push(message)
for(var i = , inserted = , m; !inserted && (m = mq[i]); ++i)
if(inserted = m.priority < message.priority)
mq.splice(i, , message)
if(!inserted) mq.push(message)
timer.pause =
}
/* @method 分发消息给订阅者
* @static
* @package
* @param {Array.<Message>} mq - 消息队列
* @param {Array} subs - 订阅者池
* @returns {Array.<Message>} - 未被响应的消息回收队列
*/
static dispatch(mq, subs){
var message, unresponsedMq = []
while(message = mq.shift()){
let found =
for(let sub of subs) if(sub.filter(message) && ++found)
sub.handler(message)
if (!found) unresponsedMq.push(message)
}
return unresponsedMq
}
static doUnsub(subs, pred){
var sub, remainSubs = []
while(sub = subs.shift()) if(pred());else
remainSubs.push(sub)
return remainSubs
}
} /**
* @class
* @private
* @description 消息类
*/
class Message{
constructor(topic, content, priority = ){
this.topic = topic
this.content = content
this.priority = priority
}
} /**
* @class
* @public
* @description 消息中转站
*/
export default class MrB{
constructor(during = ){
this.mq = []
this.subs = []
// dispatch message to subscribers, then recycle unhandled messages
this.timer = {timer: setInterval(()=>{
if (this.timer.pause) return
this.mq = Utils.dispatch(this.mq, this.subs)
}, during)}
}
/* @method 订阅消息
* @public
* @param {DOMString|Function} filter - 消息过滤器
* @param {Function} handler - 消息响应函数
* @returns {DOMString} - 订阅编号,用于退订
*/
sub(filter, handler){
var guid = (+new Date()) + '' + *Math.random()
this.subs.push({guid: guid, filter: Utils.wrapFilter(filter), handler: handler})
return guid
} /* @method 退订
* @public
* @param {DOMString|Function} filter/guid - 消息过滤器 或 订阅编号
* @param {Function} [handler] - 消息响应函数
*/
unsub(filter/*or guid*/, handler){
this.subs = Utils.doUnsub(this.subs, handler
? (sub)=>sub.filter.toString() === filter.toString() && sub.handler.toString() === handler.toString()
: (sub)=>sub.guid === filter.toString() || sub.filter.toString() === filter.toString())
} /* @method 发布消息
* @public
* @param {DOMString|Object|Message} topic - 消息主题 或 消息实体
* @param {DOMString} [content=''] - 消息内容
* @param {Number} [priority=0] - 消息优先级
*/
pub(topic, content = '', priority = ){
var message
if( === arguments.length) message = (topic.priority = topic.priority || , topic)
else message = new Message(topic, content, priority) // push message to mq
Utils.addMessage(this.mq, message, this.timer)
}
}

6. Caution                            

1. Pub/Sub模式是Messaging模式的一种,而Messaging模式是一种基于网络的架构模式(network-oriented architectural pattern),也就是说是以跨进程通信为应用范围;而Observer模式则是基于对象事件的设计模式(object-event oriented pattern),并且其应用范围是单进程内的。

2. Pub/Sub模式适用于非实时处理;

7.  Idea                            

在页面开发时我偏向于Component-Driven dev的开发模式,表面上是将页面切割为一个个功能独立的组件,本质上是将问题相关的依赖内聚,从而更好地识别、解决问题。而组件间的通信则是采用这种开发模式所必定要考虑的另一个问题。解决方案有好多,但我觉得基本原则应该是:

1. 由于组件是相互独立的松耦合结构,它们之间的通信不应该带来耦合度上扬的副作用;(若组件间通信是紧密的,应该考虑是否开发成子组件更为合适)

2. 组件间通信的通道应该是配置的,这样才能灵活地对数据流作加工。(如:写日志、数据转换、类型转换等)

而采用Pub/Sub模式,利用消息作为组件间通信的数据载体,Message Broker负责信息的过滤和路由,实现消息在组件间的流转。另外可以通过定制Message Broker实现自定义组件通信通道,以AOP方式实现基础服务功能。

而这种方式不可避免地会引入新问题:

1. Message Broker作为消息中转站具有异步处理的特性,若需要同步执行,那么则需要引入另一种方式;

2. 由于组件间松耦合,必须通过良好的日志记录方式来记录消息流转路径,否则无法debug。

8. Conclusion                         

尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4624566.html ^_^肥子John

9. Thanks                             

https://en.wikipedia.org/wiki/Messaging_pattern

https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern

http://stackoverflow.com/questions/11857325/publisher-subscriber-vs-observer

https://en.wikipedia.org/wiki/Store_and_forward

Architecture Pattern: Publish-subscribe Pattern的更多相关文章

  1. Publish/Subscribe Pattern & Vanilla JavaScript

    Publish/Subscribe Pattern & Vanilla JavaScript https://en.wikipedia.org/wiki/Publish–subscribe_p ...

  2. Publish/Subscribe Model——Notification chain——观察者模式

    内核中用的很多,整理时间子系统的时候又遇到了notification mechanism,因此做次记录: 参考:1.http://msdn.microsoft.com/en-us/library/ff ...

  3. Redis的Publish/Subscribe

    Publish/Subscribe 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布 ...

  4. Jedis的Publish/Subscribe功能的使用

    redis内置了发布/订阅功能,可以作为消息机制使用.所以这里主要使用Jedis的Publish/Subscribe功能. 1.使用Spring来配置Jedis连接池 <!-- pool配置 - ...

  5. 使用Guava EventBus构建publish/subscribe系统

    Google的Guava类库提供了EventBus,用于提供一套组件内publish/subscribe的解决方案.事件总线EventBus,用于管理事件的注册和分发.在系统中,Subscribers ...

  6. 【RabbitMQ】Publish/Subscribe

    Publish/Subscribe 在上一节我们创建了一个work queue.背后的设想为每个任务被分发给明确的消费者.这节内容我们将做一些完全不同的事情 -- 我们将发送一条消息给多个消费者.这种 ...

  7. publish/subscribe

    Pub/Sub功能 Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能.基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供 ...

  8. Mina、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    消息传递有很多种方式,请求/响应(Request/Reply)是最常用的.在前面的博文的例子中,很多都是采用请求/响应的方式,当服务器接收到消息后,会立即write回写一条消息到客户端.HTTP协议也 ...

  9. RabbitMQ(三) -- Publish/Subscribe

    RabbitMQ(三) -- Publish/Subscribe `rabbitmq`支持一对多的模式,一般称为发布/订阅.也就是说,生产者产生一条消息后,`rabbitmq`会把该消息分发给所有的消 ...

  10. ZeroMQ之Publish/Subscribe (Java)

    前面的文章介绍了比较简单的Request/Subscribe模式, 这篇文章介绍更为经典的Publish/Subscribe通信模式用来ZeroMQ的实现,其通信方式如下图: 客户端(subscrib ...

随机推荐

  1. 浅谈 facebook .net sdk 应用

    今天看了一篇非常好的文章,就放在这里与大家分享一下,顺便也给自己留一份.这段时间一直在学习MVC,另外如果大家有什么好的建议或者学习的地方,也请告知一下,谢谢. 这篇主要介绍如何应用facebook ...

  2. web前端工程师在移动互联网时代里的地位问题

    支付宝十周年推出了一个新产品:支付宝的十年账单,我也赶个时髦查看了一下我的支付宝十年账单,哎,感慨自己真是太屌丝了,不过这只是说明我使用淘宝少了,当我大规模网上购物时候,我很讨厌慢速的快递,所以我大部 ...

  3. 基础调试命令 - .dump/.dumpcap/.writemem/!runaway

    Windbg是windows平台上强大的调试器,它相对于其他常见的IDE集成的调试器有几个重要的优势, Windbg可以做内核态调试 Windbg可以脱离源代码进行调试 Windbg可以用来分析dum ...

  4. [异常解决] virtualbox从.VDI备份文件新建/恢复虚拟机(包括恢复各个备份节点)

    一.前言: ubuntu上的virtualbox中的虚拟机如果关机不当会导致整个虚拟机坏掉,而且采用各种debug方式都难以让它重新启动.这时你只能用之前备份的各个VDI文件来恢复系统了.还有另一种场 ...

  5. AngularJS快速入门指南12:模块

    AngularJS模块定义了一个application. 模块是一个application中不同部分的容器. application中的所有控制器都应该属于一个模块. 带有一个控制器的模块 下面这个a ...

  6. 去除NSString里面的空格

    NSString *password = @"12  34"; [password stringByTrimmingCharactersInSet:[NSCharacterSet ...

  7. Java连接Oracle数据库开发银行管理系统【三、实现篇】

    说明:里面的主要代码都加的有注释部分,所以代码显得很长,如果有错误的地方,谢谢指出. 注意需要导入数据库jar包 ------------------------------------------- ...

  8. Kafka与Logstash的数据采集对接 —— 看图说话,从运行机制到部署

    基于Logstash跑通Kafka还是需要注意很多东西,最重要的就是理解Kafka的原理. Logstash工作原理 由于Kafka采用解耦的设计思想,并非原始的发布订阅,生产者负责产生消息,直接推送 ...

  9. Android笔记——我的Android课的开始

    android 最底层的是什么?  硬件 介于硬件与软件之间的一个交互,你猜猜需要什么? 软件的上面一层便是各种的类库 硬件与软件之间的交互,就是需要驱动的进行. 1.android系统架构 1.Li ...

  10. tool list

    http://www.pairwise.org/tools.asp 用例设计工具  组合测试工具pict 代码调试 Findbugs  介绍及使用方法 接口测试工具 webservices studi ...