中介者模式
目的
封装很多对象互相通信的方式,允许每个对象的行为互相不同。
何时使用
- 对象间的通信很复杂
- 对象间的关系很复杂
- 需要公共控制中心
常见使用场景
- 邮件列表需要统计谁在线,让发送邮件的人可以和在线的人通信,一旦非在线的人上线,则要再去通知。中介者在其中就可以处理这种关系。
- 机场的控制中心可以避免飞机之间互相直接联系,每架飞机都和控制中心通信,由控制中心决定谁可以降落。
- DOM树上绑定事件的时候,我们可以绑定在根节点,而不是绑定到单独的node,这样根节点就像一个Mediator。
代码实现
https://github.com/benhaben/essentialjsdesignpatterns.git
下面的代码实现了一个聊天室,参与聊天的人需要互相通信,聊天室用来管理发送给谁。
var Participant = function(name) {
this.name = name;
this.chatroom = null;
};
Participant.prototype = {
send: function(message, to) {
this.chatroom.send(message, this, to);
},
receive: function(message, from) {
log.add(from.name + " to " + this.name + ": " + message);
}
};
var Chatroom = function() {
var participants = {};
return {
register: function(participant) {
participants[participant.name] = participant;
participant.chatroom = this;
},
send: function(message, from, to) {
if (to) {
to.receive(message, from);
} else {
for (key in participants) {
if (participants[key] !== from) {
participants[key].receive(message, from);
}
}
}
}
};
};
var log = (function() {
var log = "";
return {
add: function(msg) { log += msg + "\n"; },
show: function() { console.log(log); log = ""; }
}
})();
function run() {
var yoko = new Participant("Yoko");
var john = new Participant("John");
var paul = new Participant("Paul");
var ringo = new Participant("Ringo");
var chatroom = new Chatroom();
chatroom.register(yoko);
chatroom.register(john);
chatroom.register(paul);
chatroom.register(ringo);
yoko.send("All you need is love.");
yoko.send("I love you John.");
john.send("Hey, no need to broadcast", yoko);
paul.send("Ha, I heard that!");
ringo.send("Paul, what do you think?", paul);
log.show();
}
run();
上面的机制可以利用事件来实现,js的世界中,emit事件才是更常见的写法。
Event Aggregator Pattern
事件聚合器模式和Mediator很像,都是集中式管理通信,却别在于中介者控制何时去通知,有一定的业务逻辑,对象之间也是相关的。而Event Aggregator只是发出事件,然后就忘记,中间层更轻。Event Aggregator适合对象间没有直接关系的情况。
非直接的关系很适合用event aggregators,在现代应用中,视图需要互相通信的场景很常见,但是这些视图之间并没有关系。比如,一个菜单item响应click事件,需要改变内容区,但是我们并不想菜单持有内容区,这会导致难以维护的代码(想想很多视图区需要互相持有的情况)。这个时候,我们可以用事件聚合器触发一个“menu:click:foo” 事件,让“foo”对象去显示内容到内容视图。
代码实现
下面的例子让mediator监听event aggregator的事件,把两个模式集合起来观察。mediator收到事件后,可以控制相关的对象产生一些行为。但是mediator本身和事件发生者又没有关系。
/**
* Created by shenyin.sy on 17/3/17.
*/
// MenuItem(菜单,事件聚合者)和MyWorkflow(中介者)并没有直接关系
// 所以通过事件menu:click:XXX来通知,当MyWorkflow收到通知后,可以
// 在doStuff中通知相关的对象,完成某些工作流程
"use strict"
const EventEmitter = require('events');
class EventAggregator extends EventEmitter {
constructor() {
super();
this.prefix = "menu:click:";
}
clickedIt(menuName){
this.emit(this.prefix + menuName);
}
}
var eventAggregator = new EventAggregator();
//模拟一个菜单
var MenuItem ={
_menuItemName: "foo",
get menuItemName() {
return this._menuItemName;
},
set menuItemName(val) {
this._menuItemName = val
},
clickedIt: function(e){
console.log(`clickedIt : ${e}`);
// assume this triggers "menu:click:foo"
eventAggregator.emit("menu:click:" + this.menuItemName);
}
};
// ... somewhere else in the app
var MyWorkflow = function(){
eventAggregator.on("menu:click:foo", this.doStuff.bind(this));
this.tasks=[];
};
MyWorkflow.prototype.addTask = function(task){
this.tasks.push(task);
}
MyWorkflow.prototype.doStuff = function(){
// instantiate multiple objects here.
// set up event handlers for those objects.
// coordinate all of the objects into a meaningful workflow.
for(let i = 0; i < this.tasks.length; i++){
console.log(`do task : ${this.tasks[i]}`);
}
};
function run() {
//myWorkflow是一个中介者,他会通知task去做任务,并且只关心"menu:click:foo"事件
var myWorkflow = new MyWorkflow();
myWorkflow.addTask(1);
myWorkflow.addTask(2);
myWorkflow.addTask(3);
//事件发生
MenuItem.clickedIt("模拟点击菜单项");
// MenuItem1.clickedIt("模拟点击菜单项1");
// MenuItem2.clickedIt("模拟点击菜单项2");
}
run();
总结
中介者和事件聚合代码上的差别很细微,但是思想确实很远,设计模式实际上就是思想,所以明白这些差异很重要。另外传统的设计模式都是依赖接口,图表上也一般有接口,这对js来说复杂了,js实现设计模式可以说完全不需要接口,这也从反面说明,思想可以有各种实现方式。