哇——是个危险的题目,对吗?我们对于什么是本质的理解当然会随着我们对要解决问题的理解而变化。因此我不会说谎——一年前我所理解的本质很不幸并不完整,因为我确信我将要写的已经快伴随我有6个月之久。所以,这篇文章是我在发现JavaScript中成功的运用客户端消息模式的一些关键要点时的一个掠影。
1.) 理解中介者与观察者的区别
大多数人在描述任何事件/消息机制的时候喜欢套用“发布者/订阅者”(pub/sub)——但我认为这个术语不能很好的与抽象建立联系。当然,从根本上说,一些东西订阅了另一些东西发布的事件。但是发布者与订阅者在何等层次上封装在一起有可能使一个好的模式变得暗淡无光。那么,区别在什么地方呢?
观察者
观察者模式包括了被一个或多个观察者所观察的某个对象。典型的,该对象记录下所有观察者的痕迹,通常是用一个list来存储观察者注册的回调方法,这些是观察者为了接收通知而订阅的。 注意: (哦,双关语,我有多爱他们啊)(译者注:Observe 观察、注意)
var observer = { listen : function() { console.log("Yay for more cliché examples..."); } }; var elem = document.getElementById("cliche"); elem.addEventListener("click", observer.listen);
一些需要注意的事情是:
我们必须获得对此对象的直接引用
此对象必须保持一些内部的状态,保存观察者的回调痕迹
有时侦听者不会利用由此对象返回的任何参数,理论上来说,有可能有 0-n*个参数 (更多是取决于以后会变得多有趣)
* n事实上不是无限的,但为了讨论的目的,它指我们永远也达不到的极限
中介者
中介者模式在一个对象与一个观察者之间引入了一个“第三方”——有效的将二者解耦而且将他们之间如何通信封装起来。一个中介者的API可能像“发布”、“订阅”、“取消订阅”一样简单,或者某个领域范围内的实现可能被提供用来隐藏这些方法于某些更有意义的语义之中。大多数我用过的服务器端的实现更倾向于领域范围而不是更简单,但是并没有对一个通用的中介者有任何规则限制!并不罕见,有种想法认为一个通用的中介者是一种信息经纪人。无论何种情形,结果都一样——特定对象与观察者之间不再互相直接知晓:
// It's fun to be naive! var mediator = { _subs: {}, // a real subscribe would at least check to make sure the // same callback instance wasn't registered 2x. // Sheesh, where did they find this guy?! subscribe: function(topic, callback) { this._subs[topic] = this._subs[topic] || []; this._subs[topic].push(callback); }, // lolwut? No ability to pass function context? :-) publish : function(topic, data) { var subs = this._subs[topic] || []; subs.forEach(function(cb) { cb(data); }); } } var FatherTime = function(med) { this.mediator = med; }; FatherTime.prototype.wakeyWakey = function() { this.mediator.publish("alarm.clock", { time: "06:00 AM", canSnooze: "heck-no-get-up-lazy-bum" }); } var Developer = function(mediator) { this.mediator = mediator; this.mediator.subscribe("alarm.clock", this.pleaseGodNo); }; Developer.prototype.pleaseGodNo = function(data) { alert("ZOMG, it's " + data.time + ". Please just make it stop."); } var fatherTime = new FatherTime(mediator); var developer = new Developer(mediator); fatherTime.wakeyWakey();
你可能会想,除了特别纯粹的中介者实现,特定对象不再负有保存订阅者列表的责任,而且“时光老人”(FatherTime)与“开发者”(Developer)实例永远没法真正互相知道。他们只是共享了一个信息——将如我们今后所见,这是一个很重要的合约。 “很好,Jim。这对我而言仍然是发布者/订阅者,那么重点呢?我选择某个方向真的会有区别吗?”哦,继续吧,亲爱的读者们,继续吧。
2.) 了解什么时候使用中介者和观察者
使用本地的观察者和中介者,即写在组件当中的,而中介者看起来又像远程的组件间通信。不管怎样。我对待这种情况的原则虽然是——tl;dr(too long; don't read)(太长,不读了)。但无论如何,反正串联在一起最好。
要我简捷地说真是麻烦,就像把几个月来的细致体验压缩到装不下140个字的沟里。现实中回答这个问题肯定不简洁。所以有一个长版本的解释:
观察者除了关心数据映射之外还有必要引用别的项目吗?例如Backbone.View视图有各种理由直接引用它的模型。这是非常自然的关系,视图不仅要在模型改变时进行渲染,还需要调用模型的事件处理。如果段首的问题答案是”yes“,那观察者就是有意义的。
如果观察者和观察对象的关系仅仅是依赖数据,那我愿意使用中介pub/sub方式。两个Backbone.View视图或模型之间的通信,用观察者是合适的。比如控制导航菜单的视图发出的信息,是面包屑(breadcrumb)挂件需要的(响应当前的层级)。挂件不需要引用导航视图,它只需要导航视图提供信息。更关键的,导航视图也许不是唯一的信息来源,别的视图可能也可以提供。此时,中介pub/sub模式是最理想的——而且自身扩展性良好。