javascript设计模式--中介者模式(Mediator)
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>