1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>中介者模式</title>
6 </head>
7 <body>
8 <script>
9 function extend(subclass, superclass) {
10 var F = function () {
11 };
12 F.prototype = superclass.prototype;
13 subclass.prototype = new F();
14 subclass.prototype.constructor = subclass;
15 subclass.super = superclass.prototype;
16
17 if (superclass.prototype.constructor === Object.prototype.constructor) {
18 superclass.prototype.constructor = superclass;
19 }
20 }
21
22 function override(targetObj, obj, deep) {
23 if (Object.prototype.toString.call(obj) !== '[object Object]') {
24 return;
25 }
26 for (var i in obj) {
27 if (obj.hasOwnProperty(i)) {
28 if (deep === true) {
29 targetObj[i] = targetObj[i] || {};
30 rewrite(targetObj[i], obj[i], deep);
31 } else {
32 targetObj[i] = obj[i];
33 }
34 }
35 }
36 }
37 </script>
38 <script>
39 /**
40 * 中介者模式
41 *
42 * 定义:
43 * 用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
44 *
45 * 本质:封装交互
46 *
47 * 中介者模式的解决私立很简单,它通过引入一个中介对象,让其他的对象都只和中介对象交互,而中介对象知道如何和其他所有的对象交互,这样对象之间的交互关系就没有了,从而实现了对象之间的解耦。
48 * 对于中介对象而言,所有的相互交互的对象,被视为同事类,中介者对象就是来维护各个同事之间的关系,而所有的同事类都只是和中介对象交互。
49 * 每个同事对象,当自己发生变化的时候,不需要知道这会引起其他对象有什么变化,它只需要通知中介者就可以了,然后由中介者去与其他对象交互。这样松散耦合带来的好处是,除了让同事对象之间相互没有关联外,还有利于功能的修改和扩展。
50 * 有了中介者之后,所有的交互都封装到中介者对象里面,各个对象就不再需要维护这些关系了。扩展关系的时候也只需要扩展或修改中介者对象就可以了。
51 *
52 *
53 * 同事关系
54 * 在标准的中介者模式中,将使用中介者对象来交互的那些对象称为同事类,在中介者模式中,要求这些类都要继承相同的类。也就是说,这些对象从某个角度讲是同一个类型,算是兄弟对象。
55 * 正是这些兄弟对象之间的交互关系很复杂,才产生了把这些交互关系分离出来,单独做成中介者对象。
56 *
57 * 同事和中介者的关系
58 * 在中介者模式中,当一个同事对象发生了改变,需要主动通知中介者,让中介者去处理与其他同事对象相关的交互。
59 * 这就导致了同事对象和中介者对象之间必须有关系,首先是同事对象需要知道中介者对象是谁;反过来,中介者对象也需要知道相关的同事对象,这样它才能与同事对象进行交互。也就是说中介者对象和同事对象之间是相互依赖的。
60 *
61 * 如何实现同事和中介者的通信
62 * 一个同事对象发生了改变,会通知中介者对象,中介者对象会处理与其他同事的交互,这就产生了同事对象和中介者对象的相互通信。
63 * 一个实现方式是在Mediator接口中定义一个特殊的通知接口,作为一个通用的方法,让各个同事类来调用这个方法,在中介者模式结构图里画的就是这种方式。例如定义了一个通用的changed方法,并且把同事对象当作参数传入,这样在中介者对象里面,就可以去获取这个同事对象的实例数据了。
64 * 另外一种实现方式是可以采用观察者模式,把Mediator实现成为观察者,而各个同事类实现成为Subject,这样同事类发生了改变,会通知Mediator。Mediator在接到通知以后,会于相应的同事对象进行交互。
65 *
66 */
67
68 // 示例代码
69
70 (function(){
71 function Colleague(mediator){
72 this.mediator = mediator;
73 }
74 Colleague.prototype = {
75 getMediator: function(){
76 return this.mediator;
77 }
78 };
79
80 // 同事类A
81 function ColleagueA(mediator){
82 ColleagueA.super.constructor.apply(this, arguments);
83 }
84 extend(ColleagueA, Colleague);
85 ColleagueA.prototype.someOperation= function(){
86 // some code..
87
88 // 在需要跟其他同事通信的时候,通知中介者对象
89 this.getMediator().changed(this);
90 }
91
92 function ColleagueB(mediator){
93 ColleagueB.super.constructor.call(this);
94 }
95 extend(ColleagueB, Colleague);
96 ColleagueB.prototype.someOperation= function(){
97 // some code..
98
99 // 在需要跟其他同事通信的时候,通知中介者对象
100 this.getMediator().changed(this);
101 };
102
103 // 中介者
104 function Mediator(){
105 var colleagueA, colleagueB;
106
107 // 设置中介者需要了解并维护的同事A对象
108 this.setColleagueA = function(colleague){
109 colleagueA = colleague;
110 };
111
112 // 设置中介者需要了解并维护的同事B对象
113 this.setColleagueB = function(colleague){
114 colleagueB = colleague;
115 };
116
117 this.changed = function(colleague){
118 // 某个同事类发生了变化,通常需要与其他同事交互
119 // 具体协调相应的同事对象来实现协作行为
120 };
121 }
122 }());
123
124 (function(){
125 // 抽象同事类
126 var Colleague = function(mediator){
127 this.mediator = mediator;
128 };
129 Colleague.prototype = {
130 getMediator: function(){
131 return this.mediator;
132 }
133 };
134
135 // 光驱类
136 var CDDriver = function(){
137 CDDriver.super.constructor.apply(this, arguments);
138
139 this.data = '';
140 };
141 extend(CDDriver, Colleague);
142 override(CDDriver.prototype, {
143 getData: function(){
144 return this.data;
145 },
146 readCD: function(){
147 this.data = 'CDDriver Data, SoundCard Data';
148 // 通知主板,自己的状态发生了变化
149 this.getMediator().changed(this);
150 }
151 });
152
153 // CPU类
154 var CPU = function(){
155 CPU.super.constructor.apply(this, arguments);
156
157 this.videoData = '';
158 this.soundData = '';
159 };
160 extend(CPU, Colleague);
161 override(CPU.prototype, {
162 // 获取分解出来的视频数据
163 getVideoData: function(){
164 return this.videoData;
165 },
166 // 获取分解出来的声音数据
167 getSoundData: function(){
168 return this.soundData;
169 },
170 executeData: function(data){
171 var ss = data.split(',');
172 this.videoData = ss[0];
173 this.soundData = ss[1];
174 // 通知主板,CPU的工作完成
175 this.getMediator().changed(this);
176 }
177 });
178
179 // 显卡类
180 var VideoCard = function(){
181 VideoCard.super.constructor.apply(this, arguments);
182 };
183 extend(VideoCard, Colleague);
184 override(VideoCard.prototype, {
185 showData: function(data){
186 console.log('您正在观看的是:' + data);
187 }
188 });
189
190 // 声卡类
191 var SoundCard = function(){
192 SoundCard.super.constructor.apply(this, arguments);
193 };
194 extend(SoundCard, Colleague);
195 override(SoundCard.prototype, {
196 soundData: function(data){
197 console.log('画外音:' + data);
198 }
199 });
200
201 // 中介对象接口
202 var Mediator = function(){};
203 Mediator.prototype = {
204 changed: function(colleague){}
205 };
206
207 var MotherBoard = function(){
208 };
209 extend(MotherBoard, Mediator);
210 override(MotherBoard.prototype, {
211 setCdDriver: function(cdDriver){
212 this.cdDriver = cdDriver;
213 },
214 setCpu: function(cpu){
215 this.cpu = cpu;
216 },
217 setVideoCard: function(videoCard){
218 this.videoCard = videoCard;
219 },
220 setSoundCard: function(soundCard){
221 this.soundCard = soundCard;
222 },
223
224 changed: function(colleague){
225 switch(colleague) {
226 case this.cdDriver:
227 this.opeCDDriverReadData(colleague);
228 break;
229 case this.cpu:
230 this.opeCPU(colleague);
231 break;
232 default:
233 break;
234 }
235 },
236
237 opeCDDriverReadData: function(cd){
238 this.cpu.executeData(cd.getData());
239 },
240 opeCPU: function(cpu){
241 this.videoCard.showData(cpu.getVideoData());
242 this.soundCard.soundData(cpu.getSoundData());
243 }
244 });
245
246 void function run(){
247 var mediator = new MotherBoard();
248 var cd = new CDDriver(mediator);
249 var cpu = new CPU(mediator);
250 var vc = new VideoCard(mediator);
251 var sc = new SoundCard(mediator);
252
253 mediator.setCdDriver(cd);
254 mediator.setCpu(cpu);
255 mediator.setVideoCard(vc);
256 mediator.setSoundCard(sc);
257
258 cd.readCD();
259 }();
260
261 }());
262
263 /**
264 * 广义中介者
265 *
266 * 在实际开发中,经常会简化中介者模式,比如有如下简化:
267 * 1.通常会去掉同事对象的父类,这样可以让人意的对象,只要需要相互交互,就可以成为同事。
268 * 2.通常不定义Mediator接口,把具体的中介者对象实现成为单例。
269 * 3.同事对象不再持有中介者,而是在需要的时候直接获取中介者对象并调用;中介者也不再持有同事对象,而是在具体处理方法里面去创建,或者获取,或者从参数传入需要的同事对象。
270 */
271
272 // 部门与人员的交互
273 (function(){
274 // 部门类
275 var Dep = function(){
276 // 描述部门编号
277 this.depId = '';
278 // 描述部门名称
279 this.depName = '';
280 };
281 Dep.prototype = {
282 getDepId: function(){
283 return this.depId;
284 },
285 setDepId: function(depId){
286 this.depId = depId;
287 },
288 getDepName: function(){
289 return this.depName;
290 },
291 setDepName: function(depName){
292 this.depName = depName;
293 },
294 // 撤销部门
295 deleteDep: function(){
296 // 要先通过中介者去除掉所有与这个部门相关的部门和人员的关系。
297 var mediator = DepUserMediatorImpl.getInstance();
298 mediator.deleteDep(this.depId);
299
300 // 然后才能真正地清除掉这个部门
301 // 在实际开发中,这些业务功能可能会做到业务层去
302 // 而且实际开发中对于已经使用的业务数据通常不会被删除
303 // 而是会被作为历史数据保留
304 return true;
305 }
306 };
307
308 // 人员类
309 var User = function(){
310 // 人员编号
311 this.userId = '';
312 // 人员名称
313 this.userName = '';
314 };
315 User.prototype = {
316 getUserId: function(){
317 return this.userId;
318 },
319 setUserId: function(userId){
320 this.userId = userId;
321 },
322 getUserName: function(){
323 return this.userName;
324 },
325 setUserName: function(userName){
326 this.userName = userName;
327 },
328 // 人员离职
329 dimission: function(){
330 var mediator = DepUserMediatorImpl.getInstance();
331 mediator.deleteUser(this.userId);
332
333 return true;
334 }
335 };
336
337 // 描述部门和人员关系的类
338 var DepUserModel = function(){
339 // 用于部门和人员关系的编号,用作主键
340 this.depUserId = '';
341 this.depId = '';
342 this.userId = '';
343 };
344 DepUserModel.prototype = {
345 setDepUserId: function(depUserId){
346 this.depUserId = depUserId;
347 },
348 getDepUserId: function(){
349 return this.depUserId;
350 },
351 setDepId: function(depId){
352 this.depId = depId;
353 },
354 getDepId: function(){
355 return this.depId;
356 },
357 setUserId: function(userId){
358 this.userId = userId;
359 },
360 getUserId: function(){
361 return this.userId;
362 }
363 };
364
365 // 中介者对象
366 var DepUserMediatorImpl = function(){
367 // 记录部门和人员的关系
368 this.depUserCol = [];
369 this.initTestData();
370 };
371 DepUserMediatorImpl.getInstance = function(){
372 if(!(DepUserMediatorImpl.instance instanceof DepUserMediatorImpl)) {
373 DepUserMediatorImpl.instance = new DepUserMediatorImpl();
374 }
375 return DepUserMediatorImpl.instance;
376 };
377 DepUserMediatorImpl.prototype = {
378 // 初始化测试数据
379 initTestData: function(){
380 var du1 = new DepUserModel();
381 du1.setDepUserId('du1');
382 du1.setDepId('d1');
383 du1.setUserId('u1');
384 this.depUserCol.push(du1);
385
386 var du2 = new DepUserModel();
387 du2.setDepUserId('du2');
388 du2.setDepId('d1');
389 du2.setUserId('u2');
390 this.depUserCol.push(du2);
391
392 var du3 = new DepUserModel();
393 du3.setDepUserId('du3');
394 du3.setDepId('d2');
395 du3.setUserId('u3');
396 this.depUserCol.push(du3);
397
398 var du4 = new DepUserModel();
399 du4.setDepUserId('du4');
400 du4.setDepId('d2');
401 du4.setUserId('u4');
402 this.depUserCol.push(du4);
403
404 var du5 = new DepUserModel();
405 du5.setDepUserId('du5');
406 du5.setDepId('d2');
407 du5.setUserId('u1');
408 this.depUserCol.push(du5);
409 },
410 // 完成因撤销部门的操作所引起的与人员的交互,需要去除相应的关系
411 deleteDep: function(depId){
412 for(var i = 0; i < this.depUserCol.length; i++){
413 if(this.depUserCol[i].depId === depId){
414 this.depUserCol.splice(i--, 1);
415 }
416 }
417
418 return true;
419 },
420 // 完成因人员离职引起的与部门的交互
421 deleteUser: function(userId){
422 for(var i = 0; i < this.depUserCol.length; i++){
423 if(this.depUserCol[i].userId === userId){
424 this.depUserCol.splice(i--, 1);
425 }
426 }
427
428 return true;
429 },
430 // 显示一个部门想啊的所有人员
431 showDepUsers: function(dep){
432 var du;
433 for(var i = 0, len = this.depUserCol.length; i < len; i++){
434 du = this.depUserCol[i];
435 if(du.depId === dep.depId) {
436 console.log('部门编号=' + dep.depId + '下面拥有的人员,其编号是:' + du.userId);
437 }
438 }
439 },
440 // 显示一个人员所属的部门
441 showUserDeps: function(user){
442 var du;
443 for(var i = 0, len = this.depUserCol.length; i < len; i++){
444 du = this.depUserCol[i];
445 if(du.userId === user.userId) {
446 console.log('人员编号=' + user.userId + '属于部门编号是' + du.depId);
447 }
448 }
449 },
450 cjageDep: function(){
451 // ..省略
452 return false;
453 },
454 joinDep: function(colDepIds, newDep){
455 // ...省略
456 return false;
457 }
458 };
459
460 var mediator = DepUserMediatorImpl.getInstance();
461 var dep = new Dep();
462 dep.setDepId('d1');
463 var dep2 = new Dep();
464 dep2.setDepId('d2');
465 var user = new User();
466 user.setUserId('u1');
467
468 console.log('撤销部门前---------------------');
469 mediator.showUserDeps(user);
470 dep.deleteDep();
471 console.log('撤销部门后---------------------');
472 mediator.showUserDeps(user);
473
474 console.log('-----------人员离职前-----------');
475 mediator.showDepUsers(dep2);
476 user.dimission();
477 console.log('----------人员离职后------------');
478 mediator.showDepUsers(dep2);
479
480 }());
481
482 /**
483 * 中介者模式的优点:
484 * 1.松散耦合
485 * 中介者模式用过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互不依赖。这样一来,同事对象就可以独立变化和复用,从而不再像以前那样“牵一发而动全身”。
486 *
487 * 2.集中控制交互
488 * 多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那就扩展中介者对象,而各个同事类不需要做修改。
489 *
490 * 3.多对多变成一对多
491 * 没有使用中介者模式的时候,同事对象之间的关系通常是多对多的,引入中介者对象之后,中介者对象和同事对象的关系通常变成了双向的一对多,这会让对象的关系更让哦难以理解和实现。
492 *
493 *
494 * 中介者模式的缺点:
495 * 1.过度集中化
496 * 如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分复杂,而且难于管理和维护。
497 *
498 * 何时选用中介者模式
499 *
500 * 1.如果一组对象之间的通信比较复杂,导致相互依赖,结构混乱,可以采用中介者模式,吧这些对象相互的交互管理起来,各个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂。
501 *
502 * 2.如果一个对象引用很多对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交互就可以了。
503 *
504 * 相关模式
505 *
506 * 中介者模式和外观模式
507 * 这两个模式有相似的地方,也存在很大的不同。
508 * 外观模式多用来封装一个子系统内部的多个模块,目的是想子系统外部提供简单易用的接口。也就是说外观模式封装的是子系统外部和子系统内部模块间的交互;而中介者模式是提供多个平等的同事对象之间交互关系的封装,一般是用在内部实现上。
509 * 另外,外观模式是实现单向的交互,是从子系统外部来调用子系统内部,不会反着来;而中介者模式实现的是内部多个模块间多向的交互。
510 *
511 * 中介者模式和观察者模式
512 * 这两个模式可以组合使用。
513 * 中介者模式可以组合使用观察者模式,来实现当同事对象发生改变的时候,通知中介者对象,让中介对象去进行与其他相关对象的交互。
514 */
515
516 // example
517 /* Title: Mediator
518 Description: allows loose coupling between classes by being the only class that has detailed knowledge of their methods
519 */
520
521 (function(){
522 function Player(name) {
523 this.points = 0;
524 this.name = name;
525 }
526 Player.prototype.play = function () {
527 this.points += 1;
528 mediator.played();
529 };
530 var scoreboard = {
531
532 // HTML element to be updated
533 element:document.getElementById('results'),
534
535 // update the score display
536 update:function (score) {
537 var i, msg = '';
538 for (i in score) {
539 if (score.hasOwnProperty(i)) {
540 msg += '<p><strong>' + i + '<\/strong>: ';
541 msg += score[i];
542 msg += '<\/p>';
543 }
544 }
545 this.element.innerHTML = msg;
546 }
547 };
548
549 var mediator = {
550
551 // all the player
552 players:{},
553
554 // initialization
555 setup:function () {
556 var players = this.players;
557 players.home = new Player('Home');
558 players.guest = new Player('Guest');
559 },
560
561 // someone plays, update the score
562 played:function () {
563 var players = this.players,
564 score = {
565 Home:players.home.points,
566 Guest:players.guest.points
567 };
568
569 scoreboard.update(score);
570 },
571
572 // handle user interactions
573 keypress:function (e) {
574 e = e || window.event; // IE
575 if (e.which === 49) { // key "1"
576 mediator.players.home.play();
577 return;
578 }
579 if (e.which === 48) { // key "0"
580 mediator.players.guest.play();
581 return;
582 }
583 }
584 };
585
586 // go!
587 mediator.setup();
588 window.onkeypress = mediator.keypress;
589
590 // game over in 30 seconds
591 setTimeout(function () {
592 window.onkeypress = null;
593 console.log('Game over!');
594 }, 30000);
595 }());
596
597
598
599 // http://www.dofactory.com/javascript-mediator-pattern.aspx
600
601 (function(){
602 var Participant = function(name) {
603 this.name = name;
604 this.chatroom = null;
605 };
606
607 Participant.prototype = {
608 send: function(message, to) {
609 this.chatroom.send(message, this, to);
610 },
611 receive: function(message, from) {
612 log.add(from.name + " to " + this.name + ": " + message);
613 }
614 };
615
616 var Chatroom = function() {
617 var participants = {};
618 return {
619 register: function(participant) {
620 participants[participant.name] = participant;
621 participant.chatroom = this;
622 },
623 send: function(message, from, to) {
624 if (to) { // single message
625 to.receive(message, from);
626 } else { // broadcast message
627 for (key in participants) {
628 if (participants[key] !== from) {
629 participants[key].receive(message, from);
630 }
631 }
632 }
633 }
634 };
635 };
636
637 // log helper
638 var log = (function() {
639 var log = "";
640 return {
641 add: function(msg) { log += msg + "\n"; },
642 show: function() { alert(log); log = ""; }
643 }
644 })();
645
646
647 function run() {
648
649 var yoko = new Participant("Yoko");
650 var john = new Participant("John");
651 var paul = new Participant("Paul");
652 var ringo = new Participant("Ringo");
653
654 var chatroom = new Chatroom();
655 chatroom.register(yoko);
656 chatroom.register(john);
657 chatroom.register(paul);
658 chatroom.register(ringo);
659
660 yoko.send("All you need is love.");
661 yoko.send("I love you John.");
662 john.send("Hey, no need to broadcast", yoko);
663 paul.send("Ha, I heard that!");
664 ringo.send("Paul, what do you think?", paul);
665
666 log.show();
667 }
668 }());
669
670 </script>
671 </body>
672 </html>