大型JavaScript应用程序架构模式(1)(4)
Mediator模式
讲modiator之前,我们先来举个例子,机场飞行控制系统,也就是传说中的塔台,具有绝对的权利,他可以控制任何一架飞机的起飞和降落时间以及地方,而飞机和飞机之前不允许通信,也就是说塔台是机场的核心,mediator就相当于这个塔台。
mediator就是用在程序里有多个模块,而你又不想让各个模块有依赖的话,那通过mediator模式可以达到集中控制的目的。实际场景中也是,mediator封装了很多不想干的模块,让他们通过mediator联系在一起,同时也松耦合他们,使得他们之间必须通过mediator才能通信。
那mediator模式的优点是什么?那就是解耦,如果你之前对观察者模式比较了解的话,那理解下面的mediator图就相对简单多了,下图是一个high level的mediator模式图:

想想一下,各模块是发布者,mediator既是发布者又是订阅者。
Module 1向Mediator广播一个实际,说需要做某事
Mediator捕获消息以后,立即启动处理该消息需要使用的Module 2,Module 2处理结束以后返回信息给Mediator
与此同时,Mediator也启动了Module 3,当接受Module 2 返回消息的时候自动记录日志到Module 3里
可以看到,各模块之间并没有通信,另外Mediator也可以实现监控各模块状态的功能,例如如果Module 3出错了,Mediator可以暂时只想其它模块,然后重启Module 3,然后继续执行。
回顾一下,可以看到,Mediator的优点是:松耦合的模块由同一的Mediator来控制,模块只需要广播和监听事件就可以了,而模块之间不需要直接联系,另外,一次信息的处理可以使用多个模块,也方便我们以后统一的添加新的模块到现有的控制逻辑里。
确定是:由于所有的模块直接都不能直接通信,所有相对来说,性能方面可能会有少许下降,但是我认为这是值得的。
我们根据上面的讲解来一个简单的Demo:
- var mediator = (function(){
- var subscribe = function(channel, fn){
- if (!mediator.channels[channel]) mediator.channels[channel] = [];
- mediator.channels[channel].push({ context: this, callback: fn });
- return this;
- },
- publish = function(channel){
- if (!mediator.channels[channel]) return false;
- var args = Array.prototype.slice.call(arguments, 1);
- for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
- var subscription = mediator.channels[channel][i];
- subscription.callback.apply(subscription.context, args);
- }
- return this;
- };
- return {
- channels: {},
- publish: publish,
- subscribe: subscribe,
- installTo: function(obj){
- obj.subscribe = subscribe;
- obj.publish = publish;
- }
- };
- }());
然后有2个模块分别调用:
- //Pub/sub on a centralized mediator
- mediator.name = "tim";
- mediator.subscribe('nameChange', function(arg){
- console.log(this.name);
- this.name = arg;
- console.log(this.name);
- });
- mediator.publish('nameChange', 'david'); //tim, david
- //Pub/sub via third party mediator
- var obj = { name: 'sam' };
- mediator.installTo(obj);
- obj.subscribe('nameChange', function(arg){
- console.log(this.name);
- this.name = arg;
- console.log(this.name);
- });
- obj.publish('nameChange', 'john'); //sam, john
应用Facade: 应用程序核心的抽象
一个facade是作为应用程序核心的一个抽象来工作的,在mediator和模块之间负责通信,各个模块只能通过这个facade来和程序核心进行通信。作为抽象的职责是确保任何时候都能为这些模块提供一个始终如一的接口(consistent interface),和sendbox controller的角色比较类似。所有的模块组件通过它和mediator通信,所以facade需要是可靠的,可信赖的,同时作为为模块提供接口的功能,facade还需要扮演另外一个角色,那就是安全控制,也就是决定程序的哪个部分可以被一个模块访问,模块组件只能调用他们自己的方法,并且不能访问任何未授权的内容。例如,一个模块可能广播dataValidationCompletedWriteToDB,这里的安全检查需要确保该模块拥有数据库的写权限。
总之,mediator只有在facade授权检测以后才能进行信息处理。
应用Mediator:应用程序的核心
Mediator是作为应用程序核心的角色来工作的,我们简单地来说一下他的职责。最核心的工作就是管理模块的生命周期(lifecycle),当这个核心扑捉到任何信息进来的时候,他需要判断程序如何来处理——也就是说决定启动或停止哪一个或者一些模块。当一个模块开始启动的时候,它应该能否自动执行,而不需要应用程序核心来决定是否该执行(比如,是否要在DOM ready的时候才能执行),所以说需要模块自身需要去判定。
你可能还有问题,就是一个模块在什么情况下才会停止。当程序探测到一个模块失败了,或者是出错了,程序需要做决定来防止继续执行该模块里的方法,以便这个组件可以重新启动,目的主要是提高用户体验。
另外,该核心应该可以动态添加或者删除模块,而不影响其他任何功能。常见的例子是,一个模块在页面加载初期是不可用,但是用户操作以后,需要动态加载这个模块然后执行,就像Gmail里的chat聊天功能一样,从性能优化的目的来看,应该是很好理解的吧。
异常错误处理,也是由应用程序核心来处理的,另外各模块在广播信息的时候,也广播任何错误到该核心里,以便程序核心可以根据情况去停止/重启这些模块。这也是松耦合架构一个很重要的部分,我们不需要手工改变任何模块,通过mediator使用发布/订阅就可以来做到这个。
组装起来
各模块包含了程序里各种各样的功能,他们有信息需要处理的时候,发布信息通知程序(这是他们的主要职责),下面的QA小节里提到了,模块可以依赖一些DOM工具操作方法,但是不应该和系统的其它模块有依赖,一个模块不应该关注如下内容:
哪个对象或者模块订阅了这个模块发布的信息
这些对象是客户端对象还是服务器端对象
多少对象订阅了你的信息

Facade抽象应用程序的核心,避免各个模块之间直接通信,它从各模块上订阅信息,也负责授权检测,确保每个模块有用自己单独的授权。

Mediator(应用程序核心)使用mediator模式扮演发布/订阅管理器的角色,负责模块管理以及启动/停止模块执行,可以动态加载以及重启有错误的模块。

这个架构的结果是:各模块之间没有依赖,因为松耦合的应用,它们可以很容易地被测试和维护,各模块可以很容易地在其它项目里被重用,也可以在不影响程序的情况下动态添加和删除。
发布Pub/订阅Sub的延伸:自动注册事件(Automatic Event Registration)
关于自动注册事件,需要遵守一定的命名规范,比如如果一个模块发布了一个名字为messageUpdate的事件,那么所有带有messageUpdate方法的模块都会被自动执行。有好处也有利弊,具体实现方式,可以看我另外一篇帖子:jQuery自定义绑定的魔法升级版。
QA
1.有可能不使用facade或者类似的sandbox模式么?
尽管架构的大纲里提出了facade可以实现授权检查的功能,其实完全可能由mediator去做,轻型架构要做的事情其实是几乎一样的,那就是解耦,确保各模块直接和应用程序核心通信是没问题的就行。
2.你提高了模块直接不能有依赖,是否意味着不能依赖任何第三方类库(例如jQuery)。
这其实就是一个两面性的问题,我们上面说到了,一个模块也许有一些子模块,或者基础模块,比如基本的DOM操作工具类等,在这个层面上讲,我们是可以用第三方类库的,但是请确保,我们可以很容易地能否替换掉他们。
3.我喜欢这个架构,并且想开始使用这个架构,有任何代码样本可以参考么?
我打算去搞一份代码样本供大家参考,不过在这之前,你可以参考Andrew Burgees的帖子Writing Modular JavaScript 。
4.如果模块需要和应用程序核心直接通信,是否可行?
技术上来将,没有理由现在模块不能和应用程序核心直接通信,但是对于大多数应用体验来说,还是不要。既然你选择了这个架构,那就要遵守该架构所定义的规则。
原文:http://www.cnblogs.com/TomXu/archive/2011/12/14/2286225.html






