javascript设计模式-适配器模式

  1 <!DOCTYPE html>
  2 <html>
  3 <head>
  4     <title></title>
  5     <meta charset="utf-8">
  6 </head>
  7 <body>
  8 <script>
  9 /**
 10  * 适配器模式
 11  *
 12  * 定义:
 13  * 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
 14  *
 15  * 本质:
 16  * 转换匹配,复用功能
 17  *
 18  * 适配器模式可用来在现有接口和不兼容的类之间进行适配。使用这种模式的对象又叫包装器,因为它们是在用一个新的接口包装另一个对象。许多时候创建适配器对程序员和接口的设计人员都有好处。在设计类的时候往往会遇到有些接口不能与现有API一同使用的情况。借助于适配器,你不用直接修改这些类也能使用它们。在设计大型系统和遗留框架的情况下,他的优点往往比缺点更突出。
 19  *
 20  * 适配器的特点
 21  *
 22  * 适配器可以被添加到现有代码中以协调两个不同的接口。如果现有代码的接口能很好地满足需要,那就可能没有必要使用适配器。但要是现有接口对于手头的工作来说不够直观或实用,那么可以使用适配器来提供一个更简洁或更丰富的接口。
 23  * 从表面上看,适配器模式很像门面模式。它们都要对别的对象进行包装并改变其呈现的接口。二者的差别在于它们如何改变接口。门面元素展现的是一个简化的接口,它并不提供额外的选择,而且有时为了方便完成常见任务它还会做出一些假定。而适配器则要把一个接口转换为另一个接口,它并不会滤除某些能力,也不会简化接口。如果客户系统期待的API不可用,那就需要用到适配器。
 24  *
 25  * 应用适配器模式来解决问题的思路
 26  * 一般问题的根源在于接口的不兼容,功能是基本实现了,也就是说,只要让两边的接口匹配起来,就可以复用第一版的功能了。
 27  * 按照适配器模式的实现方式,可以定义一个类来实现第二版的接口,然后在内部实现的时候,转调第一版已经实现了的功能,这样就可以通过对象组合的方式,既复用了第一版已有的功能,同时又在接口上满足了第二版调用的需求,
 28  */
 29 
 30 // 例子
 31 // 已经存在的接口,这个接口需要被适配
 32 var Adaptee = function(){};
 33 Adaptee.prototype.specificRequest = function(){
 34     // 具体功能实现
 35 };
 36 
 37 // 适配器
 38 // 构造器,传入需要被适配的对象
 39 var Adapter = function(adaptee){
 40     this.adaptee = adaptee;
 41 };
 42 Adapter.prototype = {
 43     request: function(){
 44         // do sth
 45         // 可能转调已经实现了的方法,进行适配
 46         this.adaptee.specificRequest();
 47         // other thing
 48     }
 49 };
 50 
 51 var adaptee = new Adaptee();
 52 var target = new Adapter(adaptee);
 53 target.request();
 54 
 55 /*
 56 Adaptee和Target的关系
 57 
 58 适配器模式中被适配的接口Adaptee和适配成为的接口Target是没有关联的,也就是说,Adaptee和Target中的方法既可以相同,也可以不同。极端情况下两个接口里面的方法可能是完全不同的,也可能完全相同。
 59 
 60 对象组合
 61 
 62 适配器的实现方式其实是依靠对象组合的方式。通过给适配器对象组合被适配的对象,然后当客户端调用Target的时候,适配器会把相应的功能委托给被适配对象去完成。
 63 
 64 适配器模式的调用顺序
 65 
 66 target--Adapter--Adaptee
 67  */
 68 
 69 /*
 70 适配器的常见实现
 71 
 72 在实现适配器的时候,适配器通常是一个类,一般会让适配器类去实现Target接口,然后在适配器的具体实现里面调用Adaptee。也就是说适配器通常是一个Target类型,而不是Adaptee类型。
 73 
 74 智能适配器
 75 
 76 适配器也可以实现一些Adaptee没有实现,但是在Target中定义的的功能。这种情况就需要在适配器的实现里面,加入新功能的实现。这种适配器被称为智能适配器
 77 如果要使用智能适配器,一般新加入功能的实现会用到很多Adaptee的功能,相当于利用Adaptee的功能来实现更高层的功能。当然也可以完全实现新加入的功能,和已有的功能都不相关,变相地扩展了功能。
 78 
 79 适配多个Adaptee
 80 
 81 适配器在适配的时候,可以适配多个Adaptee,也就是说实现某个新的Target的功能的时候,需要调用多个模块的功能,适配多个模块的功能才能满足新接口的要求。
 82 
 83 适配器Adapter实现的复杂程度
 84 
 85 取决于Target和Adaptee的相似程度。
 86 
 87 缺省适配
 88 
 89 为一个接口提供缺省实现。有了它,就不用直接去实现接口,而是采用集成这个缺省适配对象,从而让子类可以有选择地去覆盖实现需要的方法,对于不需要的方法,使用缺省适配的方法就可以了。
 90 
 91 双向适配器
 92 
 93 适配器也可以实现双向的适配,既可以把Adaptee适配成为Target,也可以把Target适配成为Adaptee。也就是说这个适配器可以同时当作Target和Adaptee使用。
 94 在两个不同的客户需要用不用的方式查看同一个对象时,适合使用双向适配器。
 95 
 96  */
 97 
 98 /*
 99 对象适配器的实现: 依赖于对象的组合。。
100 
101 类适配器的实现: 采用多重继承对一个接口与另一个接口进行适配。
102  */
103 
104 /*
105  如果你有一个具有3个字符串参数的函数,但客户系统拥有的却是一个包含三个字符串元素的对象或数组,此时就可以用一个是配起来衔接二者。
106  */
107 var clientObject = {
108     string1: 'foo',
109     string2: 'bar',
110     string3: 'baz'
111 };
112 function interfaceMethod(str1, str2, str3) {
113     //...
114 }
115 // 适配器
116 function clientToInterfaceAdapter(o) {
117     interfaceMethod(o.string1, o.string2, o.string3);
118 }
119 clientToInterfaceAdapter(clientObject);
120 
121 
122 /*
123  适配原有实现
124 
125  在某些情况下,从客户一方对代码进行修改是不可能的。有些程序员因此索性避免创建API。如果现有接口发生了改变,那么客户代码也必须进行相应的修改后才能使用这个新接口,否则整个应用系统就有失灵的危险。在引入新接口之后,一般说来最好向客户方提供一些可为其实现新接口的适配器。
126  */
127 
128 // 示例:适配两个库
129 // 下面的例子要实现的是从Prototype库的$函数到YUI的get方法的转换
130 // 先看看他们在接口方面的差异
131 
132 // Prototype $ function
133 function $() {
134     var elements = [];
135     for (var i = 0; i < arguments.length; i++) {
136         var element = arguments[i];
137         if (typeof element === 'string') {
138             element = document.getElementById(element);
139         }
140         if (arguments.length === 1) {
141             return element;
142         }
143         elements.push(element);
144     }
145     return elements;
146 }
147 
148 // YUI get method
149 YAHOO.util.Dom.get = function (el) {
150     if (YAHOO.lang.isString(el)) {
151         return document.getElementById(el);
152     }
153     if (YAHOO.lang.isArray(el)) {
154         var c = [];
155         for (var i = 0, len = el.length; i < len; ++i) {
156             c[c.length] = YAHOO.util.Dom.get(el[i]);
157         }
158         return c;
159     }
160     if (el) {
161         return el;
162     }
163     return null;
164 };
165 
166 // Adapter
167 function PrototypeToYUIAdapter() {
168     return YAHOO.util.Dom.get(arguments);
169 }
170 function YUIToPrototypeAdapter(el) {
171     return $.apply(window, el instanceof Array ? el : [el]);
172 }
173 
174 // 有了这些适配器,现有的客户系统就可以继续使用其熟悉的API。
175 $ = PrototypeToYUIAdapter;
176 // or
177 YAHOO.util.Dom.get = YUIToPrototypeAdapter;
178 
179 
180 // 示例:适配电子邮件API
181 /*
182  本例研究的是一个web邮件API,它可以用来接收,发送邮件并执行一些别的任务。我们将采用类Ajax技术从服务器或取消息,然后将消息详情载入DOM。
183  */
184 /*
185  <!DOCTYPE html>
186  <html>
187  <head>
188  <title>Mail API Demonstration</title>
189  <meta charset="utf-8">
190  <style>
191  body {
192  font: 62.5% georgia, times, serif;
193  }
194  #doc {
195  margin: 0 auto;
196  width: 500px;
197  font-size: 1.3em;
198  }
199  </style>
200  </head>
201  <body>
202  <div id="doc">
203  <h1>Email Application Interface</h1>
204  <ul>
205  <li><a class="thread" id="msg-1" href="#">load message Sister Sonya</a></li>
206  <li><a class="thread" id="msg-2" href="#">load message Lindsey Simon</a></li>
207  <li><a class="thread" id="msg-3" href="#">load message Margaret Stoooart</a></li>
208  </ul>
209  <div id="message-pane"></div>
210  </div>
211 
212  <script src="Library.js"></ script>
213  < script>
214  */
215 // application utilities
216 var DED = {};
217 DED.util = {
218     substitute: function (s, o) {
219         return s.replace(/\{([^\{\}]*)\}/g, function (a, b) {
220             var r = o[b];
221             return typeof r === 'string' || typeof r === 'number' ? r : a;
222         });
223     },
224     asyncRequest: (function () {
225         function handleReadyState(o, callback) {
226             o.onreadystatechange = function () {
227                 if (o && o.readyState === 4) {
228                     if ((o.status >= 200 && o.status < 300) || o.status === 304) {
229                         if (callback) {
230                             callback(o);
231                         }
232                     }
233                 }
234             };
235         }
236 
237         var getXHR = function () {
238             var http;
239             try {
240                 http = new XMLHttpRequest();
241                 getXHR = function () {
242                     return new XMLHttpRequest();
243                 };
244             } catch (ex) {
245                 var msxml = [
246                     'MSXML2.XMLHTTP.3.0',
247                     'MSXML2.XMLHTTP',
248                     'Microsoft.XMLHTTP'
249                 ];
250                 for (var i = 0, len = msxml.length; i < len; i++) {
251                     try {
252                         http = new ActiveXObject(msxml[i]);
253                         getXHR = function () {
254                             return new ActiveXObject(getXHR.str);
255                         };
256                         getXHR.str = msxml[i];
257                     } catch (ex) {
258                     }
259                 }
260             }
261             return http;
262         };
263 
264         return function (method, url, callback, postData) {
265             var http = getXHR();
266             http.open(method, url, true);
267             handleReadyState(http, callback);
268             http.send(postData || null);
269             return http;
270         };
271     })()
272 };
273 
274 // dedMail application interface
275 var dedMail = (function () {
276     function request(id, type, callback) {
277         DED.util.asyncRequest(
278                 'GET',
279                 'mail-api.php?ajax=true&id=' + id + '&type=' + type,
280                 function (o) {
281                     callback(o.responseText);
282                 }
283         );
284     }
285 
286     return {
287         getMail: function (id, callback) {
288             request(id, 'all', callback);
289         },
290         sendMail: function (body, recipient) {
291             // Send mail with body text to the supplied recipient
292         },
293         save: function (id) {
294             // Save a draft copy with the supplied email ID.
295         },
296         move: function (id, destination) {
297             // Move the email to the supplied destination folder.
298         },
299         archive: function (id) {
300             // Archive the email.This can be a basic facade method that uses
301             // the move method, hard-coding the destination.
302         },
303         trash: function (id) {
304             // This can also be a facade method which moves the message to
305             // the trash folder.
306         },
307         reportSpam: function (id) {
308             // Move message to spam folder and add sender to the blacklist.
309         },
310         formatMessage: function (e) {
311             var e = e || window.event;
312             if (e.preventDefault) {
313                 e.preventDefault();
314             } else {
315                 e.returnValue = false;
316             }
317             var targetEl = e.target || e.srcElement;
318             var id = targetEl.id.toString().split('-')[1];
319             dedMail.getMail(id, function (msgObject) {
320                 var resp = eval('(' + msgObject + ')');
321                 var details = '<p><strong>From:</strong> {from}<br>';
322                 details += '<strong>Sent:</strong> {date}</p>';
323                 details += '<p><strong>Message:</strong><br>';
324                 details += '{message}</p>';
325                 $('message-pane').innerHTML = DED.util.substitute(details, resp);
326             });
327         }
328     };
329 })();
330 
331 // Set up mail implementation
332 addEvent(window, 'load', function () {
333     var threads = getElementsByClass('thread', document, 'a');
334     for (var i = 0, len = threads.length; i < len; i++) {
335         addEvent(threads[i], 'click', dedMail.formatMessage);
336     }
337 });
338 /*
339  </ script>
340  </body>
341  </html>
342  */
343 /**
344  * 从fooMail转向dedMail
345  */
346 // 先来看一段使用fooMail这个API的代码,该方法以一个回调方法为参数
347 fooMail.getMail(function (text) {
348     $('message-oane').innerHTML = text;
349 });
350 
351 // 适配器
352 var dedMailtoFooMailAdapter = {};
353 dedMailtoFooMailAdapter.getMail = function (id, callback) {
354     dedMail.getMail(id, function (resp) {
355         resp = eval('(' + resp + ')');
356         var details = '<p><strong>From:</strong> {from}<br>';
357         details += '<strong>Sent:</strong> {data}</p>';
358         callback(DED.util.substitute(details, resp));
359     });
360 };
361 // Other methods needed to adapter dedMail to the fooMail interface.
362 //...
363 
364 // Assign the adapter to the fooMail variable.
365 fooMail = dedMailtoFooMailAdapter;
366 
367 
368 /*
369  适配器模式的适用场合
370 
371  适配器适用于客户系统期待的接口与现有API提供的接口不兼容这种场合。它只能用来协调语法上的差异问题。适配器所适配的两个方法执行的应该是类似的任务,否则的话它就解决不了问题。就像桥接元素和门面元素一样,通过创建适配器,可以把抽象与其实现隔离开来,以便二者独立变化。
372  */
373 
374 /*
375  适配器之利
376 
377  适配器有助于避免大规模改写现有客户的代码。其工作机制是,用一个新的接口对现有类的接口进行包装。
378 
379  适配器之弊
380 
381  可能有些工程师不想使用适配器,其原因主要在于他们实际上需要彻底重写代码。有人认为适配器是一种不必要的开销,完全可以通过重写现有代码避免。此外适配器模式也会引入一批需要支持的新工具。如果现有API还未定形,捉着新接口还未定形,那么适配器可能不会一直管用。
382  */
383 
384 // http://www.joezimjs.com/javascript/javascript-design-patterns-adapter/
385 var AjaxLogger = {
386     sendLog: function() {
387         var data = this.urlEncode(arguments);
388 
389         jQuery.ajax({
390             url: "http://example.com/log",
391             data: data
392         });
393     },
394 
395     urlEncode: function(arg) {
396         //
397         return encodedData;
398     }
399     //
400 };
401 
402 var AjaxLoggerAdapter = {
403     log: function(arg) {
404         AjaxLogger.sendLog(arg);
405     }
406 };
407 
408 /* Adjust the LoggerFactory */
409 
410 var LoggerFactory = {
411     getLogger: function() {
412         // just gotta change what's returned here
413         return AjaxLoggerAdapter;
414     }
415     //
416 };
417 
418 
419 /*
420 相关模式
421 
422 适配器模式与桥接模式
423 
424 其实这两个模式除了结构略为相似外,功能上完全不同。
425 适配器模式是把两个或者多个接口的功能进行转换匹配;而桥接模式是让接口和实现部分相分离,以便它们可以相对于独立地变化。
426 
427 适配器模式与装饰模式
428 
429 适配器模式能模拟实现简单的装饰模式的功能,也就是为已有功能增添功能。
430 两个模式有一个很大的不同:一般适配器适配过后是需要改变接口的,如果不改接口就没有必要适配了;而装饰模式是不改变接口的,无论多少层装饰都是一个接口。因此装饰模式可以很容易地支持递归组合,而适配器就做不到,每次的接口不同,无法递归。
431 
432 适配器模式与代理模式
433 
434 适配器模式可以和代理模式组合使用。在实现适配器的时候,可以通过代理来调用Adaptee,这样可以获得更大的灵活性。
435 
436 适配器模式与抽线工厂模式
437 
438 在适配器实现的时候,通常需要得到被适配的对象。如果被适配的是一个接口,那么就可以结合一些可以创造对象实例的设计模式,来得到被适配的对象实例,比如抽象工厂模式,单例模式,工厂方法模式等。
439 
440  */
441 
442 
443 // http://www.dofactory.com/javascript-adapter-pattern.aspx
444 
445 (function(){
446     // old interface
447 function Shipping() {
448     this.request = function(zipStart, zipEnd, weight) {
449         // ...
450         return "$49.75";
451     }
452 }
453 
454 // new interface
455 function AdvancedShipping() {
456     this.login = function(credentials) { /* ... */ };
457     this.setStart = function(start) { /* ... */ };
458     this.setDestination = function(destination) { /* ... */ };
459     this.calculate = function(weight) { return "$39.50"; };
460 }
461 
462 // adapter interface
463 function ShippingAdapter(credentials) {
464     var shipping = new AdvancedShipping();
465     shipping.login(credentials);
466 
467     return {
468         request: function(zipStart, zipEnd, weight) {
469             shipping.setStart(zipStart);
470             shipping.setDestination(zipEnd);
471             return shipping.calculate(weight);
472         }
473     };
474 }
475 
476 // log helper
477 var log = (function () {
478     var log = "";
479     return {
480         add: function (msg) { log += msg + "\n"; },
481         show: function () { alert(log); log = ""; }
482     }
483 })();
484 
485 
486 function run() {
487 
488     var shipping = new Shipping();
489 
490     var credentials = {token: "30a8-6ee1"};
491     var adapter = new ShippingAdapter(credentials);
492 
493     // original shipping object and interface
494     var cost = shipping.request("78701", "10010", "2 lbs");
495     log.add("Old cost: " + cost);
496 
497     // new shipping object with adapted interface
498     cost = adapter.request("78701", "10010", "2 lbs");
499     log.add("New cost: " + cost);
500 
501     log.show();
502 }
503 }());
504 
505 </script>
506 </body>
507 </html>

posted @ 2013-03-24 14:01 LukeLin 阅读(...) 评论(...) 编辑 收藏