javascript设计模式-桥接模式

  1 <!DOCTYPE HTML>
  2 <html lang="en-US">
  3 <head>
  4     <meta charset="utf-8">
  5     <title></title>
  6 </head>
  7 <body>
  8 <script>
  9 /**
 10  * 桥接模式
 11  *
 12  * 定义:
 13  * 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
 14  *
 15  * 本质:
 16  * 分离抽象与实现
 17  *
 18  *在实现APi的时候,桥接模式非常有用。实际上,这也许是被用的最不够充分的模式之一。在所有模式中,这种模式最容易付诸实施。在设计一个JS API的时候,可以用这个模式来弱化它与使用它的的类和对象之间的耦合。按GoF的定义,桥接模式的作用在于将抽象与其实现隔离开来,以便二者独立变化。这种模式对于JS中常见的事件驱动的编程大有裨益。
 19  *
 20  * 无论是用来创建Web服务API还是普通的取值器(accessor)方法和赋值器(mutator)方法,在实现过程中桥接模式都有助于保持API代码的简洁。
 21  */
 22 
 23 // 示例:事件监听器
 24 /*
 25  桥接模式最常见和实际的应用场合之一是事件监听器回调函数。
 26  假设有一个名为getBeerById的API函数,它根据一个标示符返回有关某种啤酒的信息。那个被电击的元素很可能具有啤酒的标示符信息,它可能是作为元素自身的ID保存,也可能使作为别的自定义属性保存。
 27  */
 28 // 下面是一种做法:
 29 addEvent(element, 'click', getBeearById);
 30 function getBeearById(e) {
 31     var id = this.id;
 32     asyncRequest('GET', 'beer.uri?id=' + id, function (resp) {
 33         // callback response
 34         console.log('Requested Beer: ' + resp.responseText);
 35     });
 36 }
 37 
 38 // 这个API函数不方便做单元测试,或者在命令行环境中执行。
 39 // 作为一个优良的API,不要把它与任何特定的实现搅在一起。毕竟,我们希望所有人都能获取到啤酒的信息,
 40 function getBeearById(id, callback) {
 41     // make request for beer by ID, then return the beer data
 42     asyncRequest('GET', 'beer.uri?id=' + id, function (resp) {
 43         // callback response
 44         callback(resp.responseText);
 45     });
 46 }
 47 
 48 addEvent(element, 'click', getBeerByIdBridge);
 49 function getBeerByIdBridge(e) {
 50     getBeerById(this.id, function (beer) {
 51         console.log('Requested Beer: ' + beer);
 52     });
 53 }
 54 
 55 /*
 56  有了这层桥接元素,这个API的使用范围大大拓宽了,这给类更大的设计自由。getBeerById并没有和事件对象捆绑在一起,你也可以在单元测试中运行这个API。
 57  */
 58 
 59 // 桥接模式的其他例子
 60 /*
 61  除了在事件回调函数与接口之间进行桥接外,桥接模式也可以用来连接公开的API代码和私用的实现代码。此外,它还可以用来把多个类连结在一起。从类的角度来看,这意味着把接口作为公开的代码编写,而把类的实现作为私用代码编写。
 62 
 63  如果一个公用的接口抽象了一些也许应该属于私用性的(尽管在此情况下它不一定非得是私用的)较复杂的任务,那么可以使用桥接模式来收集某些私用性的信息。可以用一些具有特殊权利的方法作为桥梁以便访问私用变量空间,而不必冒险下到具体实现的浑水中。这一特例中的桥接性函数又称特权函数
 64  */
 65 var Public = function () {
 66     var secret = 3;
 67     this.privilegedGetter = function () {
 68         return secret;
 69     };
 70 };
 71 var o = new Public();
 72 var data = o.privilegedGetter();
 73 
 74 // 用桥接模式联结多个类
 75 var Class1 = function (a, b, c) {
 76     this.a = a;
 77     this.b = b;
 78     this.c = c;
 79 };
 80 var Class2 = function (d) {
 81     this.d = d;
 82 };
 83 var BridgeClass = function (a, b, c, d) {
 84     this.one = new Class1(a, b, c);
 85     this.two = new Class2(d);
 86 };
 87 
 88 /*
 89  本例中实际上没有客户系统要求提供数据。它只不过是用来接纳大量数据并将其发送给责任方的一种辅助性手段。此外,BridgeClass并不是一个客户系统已经实现的现有接口。引入这个类的目的只不过是要桥接一些类而已。这里使用桥接模式是为了让Class1和Class2能够独立于BridgeClass而发生改变。与门面类不同。
 90  */
 91 
 92 
 93 // 示例:构建XHR连接队列
 94 /*
 95  这个对象把请求存储在浏览器内存中的一个队列化数组中。刷新队列时每个请求都会按“先入先出”的顺序被发送给一个后端的web服务。如果次序事关重要,那么在web应用程序中使用队列化系统是有好处的。另外队列还有一个好处,任何涉及因用户输入引起的频繁动作的系统都是适用的例子。最后,连接队列可以帮助用户克服网络连接带来的不便,甚至可以允许他们离线工作。
 96  */
 97 // 添加核心工具
 98 var asyncRequest = (function () {
 99     function handleReadyState(o, callback) {
100         o.onreadystatechange = function () {
101             if (o && o.readyState === 4) {
102                 if ((o.status >= 200 && o.status < 300) || o.status === 304) {
103                     if (callback) {
104                         callback.call(o, o.responseText, o.responseXML);
105                     }
106                 }
107             }
108         };
109     }
110 
111     var getXHR = function () {
112         var http;
113         try {
114             http = new XMLHttpRequest();
115             getXHR = function () {
116                 return new XMLHttpRequest();
117             };
118         } catch (e) {
119             var msxml = [
120                 'MSXML2.XMLHTTP.3.0',
121                 'MSXML2,XMLHTTP',
122                 'Microsoft.XMLHTTP'
123             ];
124             for (var i = 0, len = msxml.length; i < len; i++) {
125                 try {
126                     http = new ActiveXObject(msxml[i]);
127                     getXHR = function () {
128                         return new ActiveXObject(getXHR.str);
129                     };
130                     getXHR.str = msxml[i];
131                     break;
132                 } catch (e) {
133                 }
134             }
135 
136         }
137         return http;
138     };
139 
140     return function (method, url, callback, postVars) {
141         var http = getXHR();
142         handleReadyState(http, callback);
143         http.open(method, url, true);
144         http.send(postVars || null);
145         return http;
146     }
147 })();
148 
149 // 扩展链式调用方法
150 Function.prototype.method = function (name, fn) {
151     this.prototype[name] = fn;
152     return this;
153 };
154 
155 // 扩展数组方法
156 if (!Array.prototype.forEach) {
157     Array.method('forEach', function (fn, thisObj) {
158         var scope = thisObj || window;
159         for (var i = 0, len = this.length; i < len; i++) {
160             fn.call(scope, this[i], i, this);
161         }
162     });
163 }
164 
165 if (!Array.prototype.filter) {
166     Array.method('filter', function (fn, thisObj) {
167         var scope = thisObj || window;
168         var a = [];
169         for (var i = 0, len = this.length; i < len; i++) {
170             if (!fn.call(scope, this[i], i, this)) {
171                 continue;
172             }
173             a.push(this[i]);
174         }
175         return a;
176     });
177 }
178 
179 // demo:
180 function isBigEnough(element, index, array) {
181     return (element >= 10);
182 }
183 var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
184 // 12, 130, 44
185 
186 
187 // 添加观察者系统
188 window.DED = window.DED || {};
189 DED.util = DED.util || {};
190 DED.util.Observer = function () {
191     this.fns = [];
192 };
193 DED.util.Observer.prototype = {
194     subscribe: function (fn) {
195         this.fns.push(fn);
196     },
197     unsubscribe: function (fn) {
198         // 过滤掉当前函数名
199         this.fns = this.fns.filter(function (el) {
200             if (el !== fn) {
201                 return el;
202             }
203         });
204     },
205     fire: function (o) {
206         // 触发(运行)当前函数
207         this.fns.forEach(function (el) {
208             el(o);
209         });
210     }
211 };
212 
213 // 开发队列的基本框架
214 /*
215  首先该队列是一个真正的队列,遵从先入先出的基本规则。
216  因为这是一个用于存储待发请求的连接队列,所以你可能希望设置“重试”的次数限制。此外,根据每个队列的请求的大小,你可能也希望能设置“超时”限制。
217  最后,我们应该能够向队列添加新请求和清空队列,当然,还要能够刷新队列。此外还应该可以从队列中删除请求,这种操作称为出列(dequeue)。
218  */
219 DED.Queue = function () {
220     // Queued requests.
221     this.queue = [];
222 
223     // Observable Objects that can notify the client of interesting
224     // moments on each DED.Queue instance.
225     this.onComplete = new DED.util.Observer();
226     this.onFailure = new DED.util.Observer();
227     this.onFlush = new DED.util.Observer();
228 
229     // Core properties that set up a frontend queueing system.
230     this.retryCount = 3;
231     this.currentRetry = 0;
232     this.paused = false;
233     this.timeout = 5000;
234     this.conn = {};
235     this.timer = {};
236 };
237 
238 DED.Queue.method('flush',function () {
239     if (!this.queue.length) {
240         return;
241     }
242     if (this.paused) {
243         this.paused = false;
244         return;
245     }
246     var that = this;
247     this.currentRetry++;
248     // 撤销请求
249     var abort = function () {
250         that.conn.abort();
251         // 如果达到重试次数,触发错误方法
252         // 否则重新请求当前队列
253         if (that.currentRetry === that.retryCount) {
254             that.onFailure.fire();
255             that.currentRetry = 0;
256         } else {
257             that.flush();
258         }
259     };
260     // 达到时间段时终止请求,再重新发送
261     this.timer = window.setTimeout(abort, that.timeout);
262     // 请求成功后的回调函数,停止重试时间器
263     // 移除队列的第一个元素
264     // 继续下一个请求,直到完成
265     var callback = function (o) {
266         window.clearTimeout(that.timer);
267         that.currentRetry = 0;
268         that.queue.shift();
269         that.onFlush.fire(o.responseText);
270         if (that.queue.length === 0) {
271             that.onComplete.fire();
272             return;
273         }
274         // recursive call to flush
275         that.flush();
276     };
277     // 发送请求
278     this.conn = asyncRequest(
279             this.queue[0]['method'],
280             this.queue[0]['url'],
281             callback,
282             this.queue[0]['param']
283     );
284 }).
285         method('setRetryCount',function (count) {
286             this.retryCount = count;
287         }).
288         method('setTimeout',function (time) {
289             this.timeout = time;
290         }).
291         method('add',function (o) {
292             this.queue.push(o);
293         }).
294         method('pause',function () {
295             this.paused = true;
296         }).
297         method('dequeue',function () {
298             this.queue.pop();
299         }).
300         method('clear', function () {
301             this.queue = [];
302         });
303 
304 /*
305  queue属性是一个数组字面量,用于保存对每一个请求的引用。add和dequeue这类方法所做的只是对这个数组进行push和pop操作。flush方法则会把请求发送出去并将它们移出数组。
306  */
307 
308 // 实现队列
309 var q = new DED.Queue();
310 // Reset our retry count to be higher for slow connections.
311 q.setRetryCount(5);
312 // Decrease timeout limit because we still want fast connections
313 q.setTimeout(1000);
314 q.add({
315     method: 'GET',
316     url: '/path/to/file.php?ajax=true'
317 });
318 q.add({
319     method: 'GET',
320     url: '/path/to/file.php?ajax=true&woe=me'
321 });
322 // Flush the queue
323 q.flush();
324 // Pause the queue, retaining the requests
325 q.pause();
326 // Clear our queue and start fresh
327 q.clear();
328 // Add two requests
329 q.add({
330     method: 'GET',
331     url: '/path/to/file.php?ajax=true'
332 });
333 q.add({
334     method: 'GET',
335     url: '/path/to/file.php?ajax=true&woe=me'
336 });
337 // Remove the last request from the queue
338 q.dequeue();
339 // Flush the queue again
340 q.flush();
341 
342 
343 // 示例:
344 addEvent(window, 'load', function () {
345     var q = new DED.Queue();
346     q.setRetryCount(5);
347     q.setTimeout(3000);
348     var items = $('items'),
349             results = $('results'),
350             queue = $('queue-items'),
351             requests = [];
352     q.onFlush.subscribe(function (data) {
353         results.innerHTML = data;
354         requests.shift();
355         queue.innerHTML = requests.toString();
356     });
357     q.onFailure.subscribe(function () {
358         results.innerHTML += '<span style="color:red;">Connection Error.</span>';
359     });
360     q.onComplete.subscribe(function () {
361         results.innerHTML += '<span style="green;">Completed</span>';
362     });
363     var actionDispatcher = function (element) {
364         switch (element) {
365             case 'flush':
366                 q.flush();
367                 break;
368             case 'dequeue':
369                 q.dequeue();
370                 requests.pop();
371                 queue.innerHTML = requests.toString();
372                 break;
373             case 'pause':
374                 q.pause();
375                 break;
376             case 'clear':
377                 q.clear();
378                 requests = [];
379                 queue.innerHTML = '';
380                 break;
381             default:
382                 break;
383         }
384     };
385     var addRequest = function (data) {
386         q.add({
387             method: 'GET',
388             url: 'bridge-connection-queue.phph?ajax=true&s=' + data,
389             params: null
390         });
391         requests.push(data);
392         queue.innerHTML = requests.toString();
393     };
394 
395     addEvent(items, 'click', function (e) {
396         e = e || window.event;
397         var src = e.target || e.srcElement;
398         try {
399             e.preventDefault();
400         } catch (e) {
401             e.returnValue = false;
402         }
403         actionDispatcher(src.id);
404     });
405 
406     var adders = $('adders');
407     addEvent(adders, 'click', function (e) {
408         e = e || window.event;
409         var src = e.target || e.srcElement;
410         try {
411             e.preventDefault();
412         } catch (e) {
413             e.returnValue = false;
414         }
415         addRequest(src.id.split('-')[1]);
416     });
417 });
418 
419 /*
420  在供用户执行刷新和暂停操作的部分,我们提供了一个动作调度函数,其作用就是桥接用户操作所包含的输入信息并将其委托给恰当的处理代码。在DOM脚本变成中这种技术也称为事件委托(event delegation)。
421 
422  判断什么地方应该使用桥接模式通常很简单。假如有下面的代码:
423  $('example').onclick=function(){
424  new RichTextEditor();
425  };
426  从中你无法看出那个编辑器要显示在什么地方,它有些什么配置选项以及应该怎样修改它。这里的要诀是要让接口“可桥接(bridgeable)”,实际上也就是可适配(adaptable)
427  */
428 /*
429  桥接模式之利
430  掌握如何在软件开发中实现桥接模式,收益的不只是你,还有那些负责维护你的作品的人。把抽象与其实现隔离开,有助于独立地管理软件的各组成部分。Bug也因此更容易查找,而软件发生严重故障的可能性也减小了。说大地,桥接元素应该是粘合每一个抽象的粘合因子。
431 
432  桥接模式之弊
433  没使用一个桥接元素都要增加一次函数调用,这对应用程序的性能会有一些负面影响。此外,他们也提高了系统的复杂程度,在出现问题时这会导致代码更难调用。大多情况下桥接模式都非常有用,但注意不要滥用。举个例来说,如果一个桥接函数被用于连接两个函数,而其中某个函数根本不会在桥接函数之外被调用,那么此时这个桥接函数就不是非要不可。
434  */
435 
436 // http://www.joezimjs.com/javascript/javascript-design-patterns-bridge/
437 var RemoteControl = function (tv) {
438     this.tv = tv;
439 
440     this.on = function () {
441         this.tv.on();
442     };
443 
444     this.off = function () {
445         this.tv.off();
446     };
447 
448     this.setChannel = function (ch) {
449         this.tv.tuneChannel(ch);
450     };
451 };
452 
453 /* Newer, Better Remote Control */
454 var PowerRemote = function (tv) {
455     this.tv = tv;
456     this.currChannel = 0;
457 
458     this.setChannel = function (ch) {
459         this.currChannel = ch;
460         this.tv.tuneChannel(ch);
461     };
462 
463     this.nextChannel = function () {
464         this.setChannel(this.currChannel + 1);
465     };
466 
467     this.prevChannel = function () {
468         this.setChannel(this.currChannel - 1);
469     };
470 };
471 PowerRemote.prototype = new RemoteControl();
472 
473 
474 /** TV Interface
475  Since there are no Interfaces in JavaScript I'm just
476  going to use comments to define what the implementors
477  should implement
478 
479  function on
480  function off
481  function tuneChannel(channel)
482  */
483 
484 /* Sony TV */
485 var SonyTV = function () {
486     this.on = function () {
487         console.log('Sony TV is on');
488     };
489 
490     this.off = function () {
491         console.log('Sony TV is off');
492     };
493 
494     this.tuneChannel = function (ch) {
495         console.log('Sony TV tuned to channel ' + ch);
496     };
497 }
498 
499 /* Toshiba TV */
500 var ToshibaTV = function () {
501     this.on = function () {
502         console.log('Welcome to Toshiba entertainment');
503     };
504 
505     this.off = function () {
506         console.log('Goodbye Toshiba user');
507     };
508 
509     this.tuneChannel = function (ch) {
510         console.log('Channel ' + ch + ' is set on your Toshiba television');
511     };
512 };
513 
514 /* Let's see it in action */
515 var sony = new SonyTV(),
516         toshiba = new ToshibaTV(),
517         std_remote = new RemoteControl(sony),
518         pwr_remote = new PowerRemote(toshiba);
519 
520 std_remote.on();            // prints "Sony TV is on"
521 std_remote.setChannel(55);  // prints "Sony TV tuned to channel 55"
522 std_remote.setChannel(20);  // prints "Sony TV tuned to channel 20"
523 std_remote.off();           // prints "Sony TV is off"
524 
525 pwr_remote.on();            // prints "Welcome to Toshiba entertainment"
526 pwr_remote.setChannel(55);  // prints "Channel 55 is set on your Toshiba television"
527 pwr_remote.nextChannel();   // prints "Channel 56 is set on your Toshiba television"
528 pwr_remote.prevChannel();   // prints "Channel 55 is set on your Toshiba television"
529 pwr_remote.off();           // prints "Goodbye Toshiba user"
530 
531 
532 // http://www.dofactory.com/javascript-bridge-pattern.aspx
533 
534 (function(){
535     // input devices
536 
537 var Gestures = function (output) {
538     this.output = output;
539     this.tap = function () { this.output.click(); }
540     this.swipe = function () { this.output.move(); }
541     this.pan = function () { this.output.drag(); }
542     this.pinch = function () { this.output.zoom(); }
543 };
544 
545 var Mouse = function (output) {
546     this.output = output;
547     this.click = function () { this.output.click(); }
548     this.move = function () { this.output.move(); }
549     this.down = function () { this.output.drag(); }
550     this.wheel = function () { this.output.zoom(); }
551 };
552 
553 // output devices
554 
555 var Screen = function () {
556     this.click = function () { log.add("Screen select"); }
557     this.move = function () { log.add("Screen move"); }
558     this.drag = function () { log.add("Screen drag"); }
559     this.zoom = function () { log.add("Screen zoom in"); }
560 };
561 
562 var Audio = function () {
563     this.click = function () { log.add("Sound oink"); }
564     this.move = function () { log.add("Sound waves"); }
565     this.drag = function () { log.add("Sound screetch"); }
566     this.zoom = function () { log.add("Sound volume up"); }
567 };
568 
569 // logging helper
570 
571 var log = (function () {
572     var log = "";
573     return {
574         add: function (msg) { log += msg + "\n"; },
575         show: function () { alert(log); log = ""; }
576     }
577 })();
578 
579 
580 function run() {
581 
582     var screen = new Screen();
583     var audio = new Audio();
584 
585     var hand = new Gestures(screen);
586     var mouse = new Mouse(audio);
587 
588     hand.tap();
589     hand.swipe();
590     hand.pinch();
591 
592     mouse.click();
593     mouse.move();
594     mouse.wheel();
595 
596     log.show();
597 }
598 }());
599 
600 </script>
601 </body>
602 </html>

 

posted @ 2013-02-01 10:14  LukeLin  阅读(977)  评论(0编辑  收藏  举报