javascript设计模式-外观模式
1 <!DOCTYPE HTML> 2 <html lang="en-US"> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 9 <script> 10 /** 11 * 门面模式 12 * 13 * 定义: 14 * 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 15 * 16 * 本质: 17 * 封装交互,简化调用 18 * 19 * 门面模式有两个作用:一是简化类的接口;二是消除类与使用它的客户代码之间的耦合。在js中,门面模式常常是开发人员最亲密的朋友。它是几乎所有js库的核心原则。通过创建一些便利方法让复杂系统变得更加简单易用,门面模式可以使库提供的工具更容易理解。使用这种模式,程序员可以间接地与一个子系统打交道,与直接访问子系统相比,这样做更不容易出错。 20 * 门面模式简化了诸如错误记录或跟踪页面视图统计数据这类常用的或重复性的任务。通过添加一些便利方法(这种方法是对原有的一些方法的组合利用),它还可以让对象的功能显得更加完善。 21 * 门面模式可用于简化复杂接口。它可以在幕后为你进行错误检查,清除不再需要的大对象,以及用一种更加易用的方式展现对象的功能。 22 * 门面模式并非必不可少,同样的任务不用它也能完成。这是一种组织性的模式,它可以用来修改类和对象的接口,使其更便于使用。它可以让程序员过的更轻松,使代码变得更容易管理。 23 * 这个模式在DOM脚本编程这种需要面对各种不一致的浏览器接口的环境中很常用。 24 * 25 * 外观模式的目的: 26 * 外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。 27 * 这点要特别注意,因为外观是当作子系统对外的接口出现的,虽然也可以在这里定义依稀子系统没有的功能,但不建议这么做,外观应该是包装已有的功能,它主要负责组合已有功能来实现客户需要,而不是添加新的实现。 28 * 29 * 表面上看就是把客户端的代码搬到Facade里面了。 30 * 31 * 对设计原则的体现 32 * 很好的体现了“最少知识原则” 33 * 34 */ 35 36 /* 37 界面: 38 主要指的是从一个组件外部来看这个组件,,能够看到什么,就、这就是这个组件的界面,也就是所说的外观, 39 比如,你从一个类外部来看这个类,那么这个类的public方法就是这个类的外观。 40 再比如,你从一个模块外观来看这个模块,那么这个模块对外的接口就是这个模块的外观,因为你只能看到这些接口,其他的模块内部实现的部分是被接口封装隔离的。 41 42 接口: 43 主要指的是外部和内部交互的一个通道,通常是指一些方法,可以是类的方法,也可以是interface方法。 44 */ 45 46 /* 47 基于GUI的操作系统就是计算机上的数据和功能的一个门面。每次点击,拖动和移动某个东西时,实际上是在跟一个门面打交道,间接地执行了一些幕后命令。 48 */ 49 50 // 跨浏览器事件侦听器 51 function addEvent(el, type, fn) { 52 if (window.addEventListener) { 53 el.addEventListener(type, fn, false); 54 } else if (window.attachEvent) { 55 el.attachEvent('on' + type, fn); 56 } else { 57 el['on' + type] = fn; 58 } 59 } 60 /* 61 addEvent函数是一个基本的门面,有了它,就有了一种为DOM节点添加事件侦听器的简便方法。 62 */ 63 64 // 用作便利方法的门面元素 65 /* 门面模式给予开发人员的另一个好处表现在对函数的组合上。这些组合而得的函数又叫便利函数(convenience function). 66 */ 67 function a(x) { 68 // do stuff here... 69 } 70 function b(y) { 71 // do stuff here... 72 } 73 function ab(x,y) { 74 a(x); 75 b(y); 76 } 77 78 // DOM中的例子 79 var DED=window.DED || {}; 80 DED.util={ 81 stopPropagation:function(e){ 82 if(e.stopPropagation) { 83 // w3 interface 84 e.stopPropagation(); 85 } else { 86 // IE's interface 87 e.cancelBubble=true; 88 } 89 }, 90 preventDefault:function(e){ 91 if(e.preventDefault) { 92 e.preventDefault(); 93 } else { 94 e.returnValue=false; 95 } 96 }, 97 // our convenience method 98 stopEvent:function(e){ 99 DED.util.stopPropagation(e); 100 DED.util.preventDefault(e); 101 } 102 }; 103 104 105 // 示例: 设置HTML元素的样式 106 function setStyle(elements,prop,val) { 107 for(var i= 0,len=elements.length;i<len;i++) { 108 document.getELementById(elements[i]).style[prop]=val; 109 } 110 } 111 setStyle(['foo'], 'position', 'absolute'); 112 113 function setCSS(el,styles){ 114 for(var prop in styls) { 115 if(!styls.hasOwnProperty(prop)) { 116 continue; 117 } 118 setStyle(el,prop,styls[prop]); 119 } 120 } 121 setCSS(['foo', 'bar'], { 122 position:'absolute', 123 top:'50px', 124 left:'300px' 125 }); 126 127 128 // 示例: 设计一个事件工具 129 DED.util.Event={ 130 getEvent:function(e){ 131 return e || window.event; 132 }, 133 getTarget:function(e){ 134 e = this.getEvent(e); 135 return e.target || e.srcElement; 136 }, 137 stopPropagation:function(e){ 138 e = this.getEvent(e); 139 if(e.stopPropagation) { 140 e.stopPropagation(); 141 } else { 142 e.cancelBubble=true; 143 } 144 }, 145 preventDefault:function(e){ 146 e = this.getEvent(e); 147 if(e.preventDefault) { 148 e.preventDefault(); 149 } else { 150 e.returnValue=false; 151 } 152 }, 153 stopEvent:function(e){ 154 this.stopPropagation(e); 155 this.preventDefault(e); 156 } 157 }; 158 159 addEvent($('example'), 'click', function(e){ 160 console.log(DED.util.Event.getTarget(e)); 161 DED.util.Event.stopEvent(e); 162 }); 163 164 /* 165 实现门面模式的一般步骤 166 找准自己的应用程序中感觉适合使用门面方法的地方后,就可以着手加入便利方法了。这些函数的名称应该仔细考虑,与它们的用途要相称。对于有几种函数组合而成的函数,一个简单的办法就是把相关函数的名称串连成一个函数名,并采用camel大写规范。 167 处理浏览器API的不一致性属于另一种情况,此时要做的就是把分支代码放在新创建的门面函数中,辅以对象检查或浏览器嗅探等技术。命名方式应该以易识别为主。 168 169 门面模式的适用场合 170 判断是否应该应用门面模式的关键在于辨认那些反复成组出现的代码。如果函数b出现在函数a之后这种情况经常出现,那么也许你应该考虑添加一个把这两个函数组合起来的门面函数。 171 在自己的核心工具代码中加入门面函数的另一个可能目的是应对js内置函数在不同浏览器中的不同表现。 172 */ 173 174 /* 175 门面模式之利 176 使用门面模式的目的就是要让程序员过的更轻松一些。编写一次组合代码,然后就可以反复使用它,这有助于节省时间和精力,他们提供了一个处理常见问题和任务的简化接口。 177 门面方法方便了开发人员,并且提供了较高层的功能,他们还能降低对外部代码的依赖程度。为应用系统的开发增加了一些额外的灵活性。通过使用门面模式,可以避免与下层子系统紧密耦合。这样就可以对这个系统进行修改该而不会影响到客户代码。 178 179 门面模式之弊 180 有时候门面元素也会带来一些不必要的额外负担。门面模式时常常会被滥用,导致小题大做。在实施一些套路之前应该认真掂量一下其实用性,因为它们不易察觉的破坏性和昂贵代价可能会是应用程序步履蹒跚。 181 对于简单的个人网站或少量营销网页来说,仅为工具提示和弹出式窗口这样一点增强行为就导入整个js库可能并不明智。 182 */ 183 184 /*** 185 外观模式的实现----------------------------------- 186 */ 187 188 /* 189 1.Facade的实现 190 对于一个子系统而言,外观类不需要很多,通常可以实现成为一个单例。也可以直接把外观中的方法实现称为静态的方法,这样就可以不需要创建外观对象的实例而直接调用,这种实现相当于把外观类当成一个辅助工具类实现。 191 */ 192 193 var Facade = function(){}; 194 Facade.prototype = { 195 test: function(){ 196 var a = new AModuleImpl(); 197 a.testA(); 198 var b = new BModuleImpl(); 199 b.testB(); 200 var c = new CModuleImpl(); 201 c.testC(); 202 } 203 }; 204 205 /* 206 2.Facade可以实现成为Interface 207 虽然Facade通常直接实现成为类,但是也可以把Facade实现成为真正的interface,只是这样会增加系统的复杂度,因为这样会需要一个Facaded的实现,还需要一个来获取Facade接口对象的工厂。 208 209 3.Facade实现成为interface的附带好处 210 如果把Facade实现成为接口,就能够有选择性地暴露接口的方法,尽量减少模块对子系统外提供的接口方法。 211 212 换句话说,一个模块的接口中定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的。有了Facade接口,那么用于仔细用内部的接口功能就不用暴露给子系统的外部了。 213 */ 214 215 var AModuleApi = function(){}; 216 AModuleApi.prototype = { 217 // 对子系统外部 218 a1: function(){}, 219 // 其他方法是用在子系统内部,与B,C模块交互用 220 a2: function(){}, 221 a3: function(){} 222 }; 223 224 var FacadeApi = { 225 // 这些是A,B,C模块对仔细用外的接口,这样外部就不需要知道A,B,C模块的存在, 226 // 只需要知道Facade接口就行了。 227 a1: function(){}, 228 b1: function(){}, 229 c1: function(){}, 230 231 // 这是对外提供的组合方法 232 test: function(){} 233 }; 234 235 /* 236 4.Facade的方法实现 237 Facade的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade本身并不进行功能的处理。Facade的方法实现只是实现一个功能的组合调用。 238 */ 239 240 /* 241 相关模式 242 243 外观模式和中介者模式 244 245 这两个模式非常类似,但是却有本质的区别。 246 中介者模式主要用来封装多个对象之间相互的交互,多用在系统内部的多个模块之间;而外观模式封装的是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用。 247 在中介者模式的实现里面,是需要实现具体的交互功能的;而外观模式的实现里面,一般是组合调用或是转调内部实现的功能,通常外观模式本身并不实现这些功能。 248 中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系全部放到中介者中去实现;而外观模式的默读是简化客户端的调用,这点和中介者模式也不同。 249 250 外观模式和单例模式 251 252 通常一个子系统只需要一个外观实例,所以外观模式可以和单例模式组合使用,吧Facade类实现成为单例。当然,要把外观类的构造器私有化,然后把提供给客户端的方法实现成为静态的。 253 254 外观模式和抽象工厂模式 255 外观模式的外观类通常需要和系统内部的多个模块交互,每个模块一般都有自己的接口,所以在外观类的具体实现里面,需要获取这些接口,然后组合这些接口来完成客户端的功能。 256 那么怎么获取这些接口呢?就可以和抽象工厂一起使用,外观类通过抽象工厂来获取所需要的接口,而抽象工厂也可以把模块内部的实例对Facade进行屏蔽,也就是说Facade也仅仅只是知道它从模块中获取它需要的功能,模块内部的细节,Facade也不知道。 257 */ 258 259 260 // http://www.dofactory.com/javascript-facade-pattern.aspx 261 262 var Mortgage = function(name) { 263 this.name = name; 264 } 265 266 Mortgage.prototype = { 267 applyFor: function(amount) { 268 269 // access multiple subsystems... 270 271 var result = "approved"; 272 if (!new Bank().verify(this.name, amount)) { 273 result = "denied"; 274 } else if (!new Credit().get(this.name)) { 275 result = "denied"; 276 } else if (!new Background().check(this.name)) { 277 result = "denied"; 278 } 279 280 return this.name + " has been " + result + 281 " for a " + amount + " mortgage"; 282 } 283 } 284 285 var Bank = function() { 286 this.verify = function(name, amount) { 287 // complex logic ... 288 return true; 289 } 290 } 291 var Credit = function() { 292 this.get = function(name) { 293 // complex logic ... 294 return true; 295 } 296 } 297 var Background = function() { 298 this.check = function(name) { 299 // complex logic ... 300 return true; 301 } 302 } 303 304 305 function run() { 306 307 var mortgage = new Mortgage("Joan Templeton"); 308 var result = mortgage.applyFor("$100,000"); 309 310 alert(result); 311 } 312 313 </script> 314 </body> 315 </html>