1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>工厂方法模式</title> 6 </head> 7 <body> 8 9 <script> 10 11 /** 12 * 工厂模式 13 * 14 * 一个类或对象中往往会包含内部的对象。在创建这种成员对象时,你可能习惯于使用常规方式,也即用new关键字和类构造函数。问题在于这会导致相关的两个类之间产生依赖性。本章将讲述一种有助于消除这两个类之间的依赖性的模式,它使用一个方法来决定究竟要实例化哪个具体的类。简单工厂模式另外使用一个类或者静态方法(通常是一个单体)来生成实例,复杂工厂模式则使用子类来决定一个成员变量应该是哪个具体的类的实例。 15 */ 16 17 /** 18 * 简单工厂 19 * 20 * 定义: 21 * 提供一个创建实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口,抽象类或具体类。 22 * 23 * 本质: 24 * 简单工厂方法的内部主要实现的功能是“选择合适的实现类”。本质是“选择实现”。 25 * 26 * 命名建议: 27 * 类名称建议为“模块名称+Factory”。 28 * 方法名称通常为“get+接口名称”或者是“create+ 29 * 接口名称” 30 */ 31 32 // 假设你想开几个自行车商店,每个店都有几种型号的自行车出售 33 /* BycycleShop class */ 34 35 var BicycleShop = function () { 36 }; 37 BicycleShop.prototype = { 38 sellBicycle: function (model) { 39 var bicycle; 40 41 switch (model) { 42 case 'The Speedster': 43 bicycle = new Speedster(); 44 break; 45 case 'The Lowrider': 46 bicycle = new Lowrider(); 47 break; 48 case 'The Comfort Cruiser': 49 default: 50 bicycle = new ComfortCruiser(); 51 } 52 bicycle.assemble(); 53 bicycle.wash(); 54 55 return bicycle; 56 } 57 }; 58 /* 59 sellBicycle方法根据所要求的自行车型号用switch语句创建一个自行车的实例。各种型号的自行车实例可以互换使用,因为它们都实现了Bicycle接口. 60 接口在工厂模式中起着很重要的作用。如果不对对象进行某种类型检查以确保其实现了必须的方法,工厂模式带来的好处也就所剩无几了。在所有这些例子中,你可以创建一些对象并且对它们一视同仁,那是因为你可以确信它们都实现了同样一批方法。 61 */ 62 63 /* Speedster class */ 64 var Speedster = function () { 65 // implements Bicycle 66 //.. 67 }; 68 Speedster.prototype = { 69 assemble: function () { 70 }, 71 wash: function () { 72 }, 73 ride: function () { 74 }, 75 repair: function () { 76 } 77 }; 78 79 /* 要出售某种型号的自行车,只要调用sellBycycle方法即可 */ 80 var californiaCruisers = new BicycleShop(); 81 var yourNewBike = californiaCruisers.sellBicycle('The Speedster'); 82 83 /* 84 如果你想在供货目录中加入一款新车型又会怎么样呢?你得为此修改BicycleShop的代码,哪怕这个类的实际功能实际上并没有发生改变---依旧是创建一个自行车的新实例,组装它,清洗它,然后把它交给顾客。更好的解决办法是把sellBicycle方法中“创建新实例”这部分工作转交给一个简单工厂对象。 85 */ 86 /* BicycleFactory namespace */ 87 var BicycleFactory = { 88 createBicycle: function (model) { 89 var bicycle; 90 91 switch (model) { 92 case 'The Speedster': 93 bicycle = new Speedster(); 94 break; 95 case 'The Lowrider': 96 bicycle = new Lowrider(); 97 break; 98 /*--------------------------------*/ 99 //添加新车型 100 case 'The Flatlander': 101 biccle = new Flatlander(); 102 break; 103 /*--------------------------------*/ 104 case 'The Comfort Cruiser': 105 default: 106 bicycle = new AcmeComfortCruiser(); 107 } 108 109 return bicycle; 110 } 111 }; 112 113 /* 114 BicycleFactory是一个单体,用来把createBicycle方法封装在一个命名空间中。这个方法返回一个实现了Bicycle接口的对象,然后你可以照常对其进行组装和清洗。 115 */ 116 /* BicycleShop class, improved */ 117 var BicycleShop = function () { 118 }; 119 BicycleShop.prototype = { 120 sellBicycle: function (model) { 121 var bicycle = BicycleFactory.createBicycle(model); 122 123 bicycle.assemble(); 124 bicycle.wash(); 125 126 return bicycle; 127 } 128 }; 129 /* 130 BicycleFactory就是简单工厂的一个很好的例子。这种模式把成员对象的创建工作转交给一个外部对象。这个外部对象可以像本例中一样是一个简单的命名空间,也可以是一个类的实例。如果负责创建实例的方法的逻辑不会发生变化,那么一般用单体或静态类方法创建这些成员实例是合乎情理的。但如果你要提供几种不同品牌的自行车,那么更恰当的做法是把这个创建方法实现在一个类中,并从该类派生出一些子类。 131 */ 132 /* 133 简单工厂能帮助我们真正地面向接口编程. 134 */ 135 136 /* 137 框架 138 139 框架就是能完成一定功能的半成品软件。 140 141 框架能干什么? 142 143 1.能完成一定功能,加快应用开发进度。 144 2.给我们一个精良的程序架构。 145 146 框架和设计模式的关系 147 148 1.设计模式比框架更抽象。 149 2.设计模式是比框架更小的体系结构元素。 150 3.框架比设计模式更加特例化。(框架总是针对特定领域的,设计模式更加注重从思想上,方法上来解决问题,更加通用化) 151 */ 152 153 154 /** 155 * 工厂方法模式 156 * 157 * 定义: 158 * 定义一个用于创建对象的接口,让子类决定实例化哪个类,Factory Method使一个类的实例化延迟到其子类。 159 * 160 * 本质: 161 * 延迟到子类来选择实现 162 * 163 * 真正的工厂模式与简单工厂模式的区别在于,它不是另外使用一个类或对象来创建自行车,而是使用一个子类。按照正式定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。 164 * 165 * 在工厂方法模式里面,客户端要么使用Creator对象,要么使用Creator创建的对象,一般客户不直接使用工厂方法,当然也可以直接把工厂方法暴露给客户端操作,但是一般不这么做。 166 */ 167 168 /* 169 我们可以把BicycleShop设计为抽象类,让子类根据各自的进货渠道实现其createBicycle方法。 170 */ 171 /* BicycleShop class (abstract) */ 172 var BicycleShop = function () { 173 }; 174 BicycleShop.prototype = { 175 sellBicycle: function (model) { 176 var bicycle = this.createBicycle(model); 177 178 bicycle.assemble(); 179 bicycle.wash(); 180 181 return bicycle; 182 }, 183 createBicycle: function (model) { 184 throw new Error('Unsupported operation on an abstract class.'); 185 } 186 }; 187 188 /* 189 这个类中定义了createBicycle方法,但真要调用这个方法的话,会抛出一个错误。现在BicycleShop是一个抽象类,它不能被实例化,只能用来派生子类。设计一个经销特定自行车生产厂家产品的子类需要扩展BicycleShop类,重定义其中的createBicycle方法。 190 下面是两个子类的例子,其中一个子类代表的商店从Acme公司进货,而另一个则从General Products公司进货 191 */ 192 function extend(subClass, superClass) { 193 function F() { 194 } 195 196 F.prototype = superClass.prototype; 197 subClass.prototype = new F(); 198 subClass.prototype.constructor = subClass; 199 200 subClass.superclass = superClass.prototype; 201 202 if (superClass.prototype.constructor === Object.prototype.constructor) { 203 superClass.prototype.constructor = superClass; 204 } 205 } 206 /* AcmeBicycleShop class */ 207 var AcmeBicycleShop = function () { 208 }; 209 extend(AcmeBicycleShop, BicycleShop); 210 AcmeBicycleShop.prototype.createBicycle = function (model) { 211 var bicycle; 212 213 switch (model) { 214 case 'The Speedster': 215 bicycle = new Speedster(); 216 break; 217 case 'The Lowrider': 218 bicycle = new Lowrider(); 219 break; 220 case 'The Comfort Cruiser': 221 default: 222 bicycle = new ComfortCruiser(); 223 } 224 Interface.ensureImplements(bicycle, Bicycle); 225 return bicycle; 226 }; 227 228 /* GeneralProductsBicycleShop class */ 229 var GeneralProductsBicycleShop = function () { 230 }; 231 extend(GeneralProductsBicycleShop, BicycleShop); 232 GeneralProductsBicycleShop.prototype.createBicycle = function (model) { 233 var bicycle; 234 235 switch (model) { 236 case 'The Speedster': 237 bicycle = new Speedster(); 238 break; 239 case 'The Lowrider': 240 bicycle = new Lowrider(); 241 break; 242 case 'The Comfort Cruiser': 243 default: 244 bicycle = new ComfortCruiser(); 245 } 246 247 return bicycle; 248 }; 249 /* 250 这些工厂方法生成的对象都实现了Bicycle接口,所以在其他代码眼里它们完全可以互换,自行车的销售工作还是与以前一样,只是现在所开的商店可以是Acme或General Products自行车专卖店。 251 */ 252 var alecsCruisers = new AcmeBicycleShop(); 253 var yourNewBike = alecsCruisers.sellBicycle('The Lowrider'); 254 255 var bobsCruisers = new GeneralProductsBicycleShop(); 256 var yourSecondNewBike = bobsCruisers.sellBicycle('The Lowrider'); 257 258 /* 259 增加对其他生产厂家的支持很简单。只要再创建一个BicycleShop的子类并重定义其createBicycle工厂方法即可。我们也可以对各个子类进行修改,以支持相关厂家其他型号的产品。这是工厂模式最重要的特点。对Bicycle进行一般性操作的代码可以全部写在父类BicycleShop中,而对具体的Bicycle对象进行实例化的工作则被留到子类中。一般性的代码被集中在一个位置,而个体性的代码则被封装在子类中。 260 */ 261 262 /* 263 工厂模式的适用场合 264 265 * 动态实现: 266 如果需要像前面自行车的例子一样,创建一些用不同方式实现同一接口的对象,那么可以使用一个工厂方法或简单工厂对象来简化选择实现的过程。这种选择可以是明确进行的也可以是隐含的。这是js中使用工厂模式的最常见的原因。 267 268 * 节省设置开销: 269 如果对象需要进行复杂并且批次相关的设置,那么使用工厂模式可以减少每种对象所需的代码量。如果这种设置只需要为特定类型的所有实例执行一次即可,这种作用尤其突出。把这种设置代码放到类的构造函数中并不是一种高效的做法,这是因为即使设置工作已经完成,每次创建新实例的时候这些代码还是会执行,而且这样做会把设置代码分散到不同的类中。工厂方法非常适合于这种场合。它可以在实例化所有需要的对象之前先一次性地进行设置。无论有多少不同的类都会被实例化,这种办法都可以让设置代码集中在一个地方。 270 如果所用的类需要加载外部库的话,这尤其有用。工厂方法可以对这些库进行检查并动态加载那些未找到的库。这些设置代码只存在于一个地方,因此以后改起来也方便得多。 271 272 * 用许多小型对象组成一个大对象 273 工厂方法可以用来创建封装了许多较小对象的对象。考虑一下自行车对象的构造函数。自行车包含着许多更小的子系统:车轮,车架,传动部件等。如果你不想让某个子系统与较大的那个对象形成强耦合,而是想在运行时从许多子系统中进行挑选的话,那么工厂方法是一个理想的选择。使用这种技术,某天你可以为售出的所有自行车配上某种链条,要是第二天找到另一种更中意的链条,可以改而采用这个新品种。 274 275 */ 276 277 278 // 示例:XHR工厂 279 /* 简单工厂非常适合这种场合,它可以用来根据浏览器能力的不同生成一个XMLHttpRequest或ActiveXObject实例 280 */ 281 /* AjaxHandler interface */ 282 var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']); 283 284 /* SimpleHandler class */ 285 var SimpleHandler = function () { 286 }; // implements AjaxHandler 287 SimpleHandler.prototype = { 288 request: function (method, url, callback, postVars) { 289 var xhr = this.createXhrObject(); 290 xhr.onreadystatechange = function () { 291 if (xhr.readyState !== 4) { 292 return; 293 } 294 ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) ? 295 callback.success(xhr.responseText, xhr.responseXML) : 296 callback.failure(xhr.status); 297 }; 298 xhr.open(method, url, true); 299 if (method !== 'POST') { 300 postVars = null; 301 } 302 xhr.send(postVars); 303 }, 304 createXhrObject: function () { // Factory method 305 var methods = [ 306 function () { 307 return new XMLHttpRequest(); 308 }, 309 function () { 310 return new ActiveXObject('MSXML2.XMLHTTP.3.0'); 311 }, 312 function () { 313 return new ActiveXObject('MSXML.XMLHTTP'); 314 }, 315 function () { 316 return new ActiveXObject('Microsoft.XMLHTTP'); 317 } 318 ]; 319 320 for (var i = 0, len = methods.length; i < len; i++) { 321 try { 322 methods[i](); 323 } catch (e) { 324 continue; 325 } 326 // if we reach this point,method[i] worked 327 this.createXhrObject = methods[i]; // Memoize the method 328 return methods[i](); 329 } 330 331 // if we reach this point,none of the methods worked 332 throw new Error('SimpleHandler:Could not create an XHR object.'); 333 } 334 }; 335 336 /* 337 createXhrObject这个工厂方法根据当前环境的具体情况返回一个XHR对象。在首次执行时,它会依次尝试三种用于创建XHR对象的不同方法,一旦遇到一种管用的,它就会返回创建的对象并将自身改为用以创建那个对象的函数。这个新函数于是变成了createXhrObject方法,这种技术被称为memoizing,它可以用来创建存储着复杂计算的函数和方法,以免再次进行这种计算。所有那些复杂的设置代码只会在方法首次执行时被调用一次,此后就只有针对当前浏览器的代码会被执行。 338 */ 339 340 // 用SimpleHandler类发起一部请求的过程 341 var myHandler = new SimpleHandler(); 342 var callback = { 343 success: function (responseText) { 344 alert('Success:' + responseText); 345 }, 346 failure: function (statusCode) { 347 alert('Failure:' + statusCode); 348 } 349 }; 350 myHandler.request('GET', 'script.php', callback); 351 352 353 // 专用型连接对象 354 /* 355 这个例子可以进一步扩展,把工厂模式用在两个地方,以便根据网络条件创建专门的请求对象。在创建XHR对象时已经用过了简单工厂模式。另一个工厂则用来返回各种处理器类,它们都派生自SimpleHandler。 356 首先要做的是创建两个新的处理器类。QueuedHandler会在发起新的请求之前先确保所有请求都已经成功处理。而OfflineHandler则会在用户处于离线状态时把请求缓存起来。 357 */ 358 /* QueueHandler class */ 359 var QueuedHandler = function () { // implements AjaxHandler 360 this.queue = []; // 保存请求队列 361 this.requestInProgress = false; // 请求进程,是否完成 362 this.retryDelay = 5; // In seconds 延时(秒) 363 }; 364 extend(QueuedHandler, SimpleHandler); 365 /** 366 * 367 * @param {Boolean} override 是否需要覆盖请求 368 */ 369 QueuedHandler.prototype.request = function (method, url, callback, postVars, override) { 370 if (this.requestInProgress && !override) { 371 // 其它请求正在进行中且不覆盖请求 372 this.queue.push({ 373 method: method, 374 url: url, 375 callback: callback, 376 postVars: postVars 377 }); 378 } else { 379 // 当前没有请求进行,立刻执行该请求 380 // 执行成功后递归 381 this.requestInProgress = true; 382 var xhr = this.createXhrObject(); 383 var that = this; 384 xhr.onreadystatechange = function () { 385 if (xhr.readyState !== 4) { 386 return; 387 } 388 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { 389 // 该请求成功,执行对应队列的成功方法 390 callback.success(xhr.responseText, xhr.responseXML); 391 // 处理位于最前队列的请求 392 that.advanceQueue(); 393 } else { 394 callback.failure(xhr.status); 395 setTimeout(function () { 396 that.request(method, url, callback, postVars, true); 397 }, that.retryDelay * 1000); 398 } 399 }; 400 xhr.open(method, url, true); 401 if (method !== 'POST') { 402 postVars = null; 403 } 404 xhr.send(postVars); 405 } 406 }; 407 408 QueuedHandler.prototype.advanceQueue = function () { 409 if (this.queue.length === 0) { 410 this.requestInProgress = false; 411 return; 412 } 413 var req = this.queue.shift(); 414 this.request(req.method, req.url, req.callback, req.postVars, true); 415 }; 416 417 /* 418 QueueHandler的request方法与SimpleHandler的看上去差不多,但在允许发起新请求之前它会先检查一下,以确保当前没有别的请求正在处理。如果有哪个请求未能成功处理,那么它还会在指定的时间间隔之后再次重复这个请求,直到该请求被成功处理为止。 419 */ 420 421 /* OfflineHandler class */ 422 var OfflineHandler = function () { // implements AjaxHandler 423 this.storedRequests = []; 424 }; 425 extend(OfflineHandler, SimpleHandler); 426 OfflineHandler.prototype.request = function (method, url, callback, postVars) { 427 if (XhrManager.isOffline()) { 428 // Store the requests until we are online 429 this.storedRequests.push({ 430 method: method, 431 url: url, 432 callback: callback, 433 postVars: postVars 434 }); 435 } else { 436 // Call SimpleHandler's request method if we are online 437 this.flushStoredRequests(); 438 OfflineHandler.superclass.request(method, url, callback, postVars); 439 } 440 }; 441 OfflineHandler.prototype.flushStoredRequests = function () { 442 for (var i = 0, len = this.storedRequests.length; i < len; i++) { 443 var req = this.storedRequests[i]; 444 OfflineHandler.superclass.request(req.method, req.url, req.callback, req.postVars); 445 } 446 }; 447 /* 448 XhrManager.isOffline方法的作用在于判断用户时都处于在线状态。OfflineHandler只有在用户处于在线状态时才会使用SimpleHandler的request方法实际发起请求。而且一旦探测到用户处于在线状态,它还会立即执行所有缓存中的请求。 449 */ 450 451 /* XhrManager singleton */ 452 var XhrManager = { 453 createXhrHandler: function () { 454 var xhr; 455 if (this.isOffline()) { 456 xhr = new OfflineHandler(); 457 } else if (this.isHighLatency()) { 458 xhr = new QueuedHandler(); 459 } else { 460 xhr = new SimpleHandler(); 461 } 462 463 Interface.ensureImplements(xhr, AjaxHandler); 464 return xhr; 465 }, 466 test: function () { 467 this.timer = {}; 468 this.timeout = 8000; 469 var that = this; 470 var myHandler = new SimpleHandler(); 471 var callback = { 472 success: function (responseText) { 473 window.clearTimeout(that.timer); 474 alert('Success:' + responseText); 475 }, 476 failure: function (statusCode) { 477 alert('Failure:' + statusCode); 478 } 479 }; 480 481 this.timer = window.setTimeout(that.test, that.timeout); 482 myHandler.request('GET', 'script.php', callback); 483 }, 484 isOffline: function () { 485 // Do a quick request with SimpleHandler and 486 // see if it succeeds 487 488 489 }, 490 isHighLatency: function () { 491 // Do a series of requests with SimpleHandler and 492 // time the responses.Best done once, as a 493 // branching function. 494 } 495 }; 496 497 var myHandler = XhrManager.createXhrHandler(); 498 var callback = { 499 success: function (responseText) { 500 alert('Success: ' + responseText); 501 }, 502 failure: function (statusCode) { 503 alert('Failure: ' + statusCode); 504 } 505 }; 506 myHandler.request('GET', 'script.php', callback); 507 508 /* 509 createXhrHandler方法返回的各种对象都具有我们所需要的一些方法。而且,因为它们都派生自SimpleHandler,所以createXhrObject这个复杂的方法只需要在这个类中实现一次即可,那些子类可以使用这个方法。OfflineHandler中还有多处使用了SimpleHandler的request方法,这进一步实现了代码的重用。 510 */ 511 512 // 工厂模式之利 513 /* 514 工厂模式的主要好处在于消除对象间的耦合。通过使用工厂方法而不是new关键字及具体类。你可以把所有实例化代码集中在一个位置。这可以大大简化更换所用的类或在运行期间动态选择所用的类的工作。在派生子类时它也提供了更大的灵活性。使用工厂模式,你可以创建一个抽象的父类,然后在子类中创建工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。 515 所有这些好处都与面向对象涉及的这两条原则有关:弱化对象间的耦合;防止代码的重用。这些都有助于创建模块化的代码。 516 */ 517 518 // 工厂模式之弊 519 /* 520 如果根本不可能另外换用一个类,或者不需要在运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。大多数类最好使用new关键字和构造函数公开地进行实例化。这可以让代码更简单易读。你可以一眼就看到调用的是什么构造函数,而不必去查看某个工厂方法以便知道实例化的是什么类。工厂方法在其使用场合非常有用,但切勿滥用。 521 */ 522 523 /* 524 相关模式 525 526 * 简单工厂和抽象工厂模式 527 * 528 简单工厂使用来选择实现的,可以选择任意接口的实现。一个简单工厂可以有多个用于选择并创建对象的方法,多个方法创建的对象可以有关系也可以没有关系。 529 抽象工厂模式使用来选择产品簇的实现的,也就是说一般抽象工厂里面有多个用于选择并创建对象的方法,但是这些方法所创建的对象之间通常是有关系的,这些被创建的对象通常是构成一个产品簇所需要的部件对象。 530 所以从某种意义上来说,简单工厂和抽象工厂是类似的,如果抽象工厂退化成为只有一个实现,不分层次,那么就相当于简单工厂了。 531 532 * 简单工厂和工厂方法模式 533 * 534 工厂方法的本质也使用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现。 535 如果把工厂方法中选择的是先放到父类直接实现,那就等于简单工厂。 536 537 *简单工厂和能创建对象实例的模式 538 * 539 简单工厂可以跟其他任何能够具体的创建对象实例的模式配合使用,比如:单例模式,原型模式,生成器模式等。 540 541 * 工厂方法和模板方法模式 542 * 543 这两个模式外观类似,都有一个抽象类,然后由子类来提供一些实现,但是工厂方法模式的子类专注的是创建产品对象,而模板方法模式的子类专注的是为固定算法骨架提供某些步骤的实现。 544 这两个模式可以组合使用,通常在模板方法模式里面,使用工厂方法来创建末班方法需要的对象。 545 */ 546 547 548 /* AjaxHandler interface */ 549 var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']); 550 551 /* SimpleHandler class */ 552 var SimpleHandler = function () { 553 }; // implements AjaxHandler 554 SimpleHandler.prototype = { 555 request: function (method, url, callback, postVars) { 556 var xhr = this.createXhrObject(); 557 xhr.onreadystatechange = function () { 558 if (xhr.readyState !== 4) { 559 return; 560 } 561 ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) ? 562 callback.success(xhr.responseText, xhr.responseXML) : 563 callback.failure(xhr.status); 564 }; 565 xhr.open(method, url, true); 566 if (method !== 'POST') { 567 postVars = null; 568 } 569 xhr.send(postVars); 570 }, 571 createXhrObject: function () { // Factory method 572 var methods = [ 573 function () { 574 return new XMLHttpRequest(); 575 }, 576 function () { 577 return new ActiveXObject('Msxml2.XMLHTTP'); 578 }, 579 function () { 580 return new ActiveXObject('Microsoft.XMLHTTP'); 581 } 582 ]; 583 584 for (var i = 0, len = methods.length; i < len; i++) { 585 try { 586 methods[i](); 587 } catch (e) { 588 continue; 589 } 590 // if we reach this point,method[i] worked 591 this.createXhrObject = methods[i]; // Memoize the method 592 return methods[i](); 593 } 594 595 // if we reach this point,none of the methods worked 596 throw new Error('SimpleHandler:Could not create an XHR object.'); 597 } 598 }; 599 600 function $() { 601 var elements = []; 602 for (var i = 0; i < arguments.length; i++) { 603 var element = arguments[i]; 604 if (typeof element == 'string') { 605 element = document.getElementById(element); 606 } 607 if (arguments.length === 1) { 608 return element; 609 } 610 elements.push(element); 611 } 612 return elements; 613 } 614 615 /*------------------ 616 显示类,它把输出内容包装为一个无序列表 617 --------------------*/ 618 /* DisplayModule interface */ 619 var DisplayModule = new Interface('DisplayModule', ['append', 'remove', 'clear']); 620 621 /* ListDisplay class. */ 622 var ListDisplay = function (id, parent) { 623 // implements Display 624 this.list = document.createElement('ul'); 625 this.list.id = id; 626 parent.appendChild(this.list); 627 }; 628 ListDisplay.prototype = { 629 append: function (text) { 630 var newEl = document.createElement('li'); 631 this.list.appendChild(newEl); 632 newEl.innerHTML = text; 633 return newEl; 634 }, 635 remove: function (el) { 636 this.list.removeChild(el); 637 }, 638 clear: function () { 639 this.list.innerHTML = ''; 640 } 641 }; 642 643 /* 配置对象,它包含着一些供阅读器类及其成员对象使用的设置 */ 644 /* Configuration */ 645 var conf = { 646 id: 'cnn-top-stories', 647 feedUrl: 'http://rss.cnn.com/rss/cnn_topstories.rss', 648 updateInterval: 60, // In seconds 649 parent: $('feed-readers') 650 }; 651 652 /* FeedReader类用XHR处理器从RSS源获取XML格式的数据并用一个内部方法对其进行解析,然后用显示模块将解析出来的信息输出到网页上 */ 653 /* FeedReader class. */ 654 var FeedReader = function (display, xhrHandler, conf) { 655 this.display = display; 656 this.xhrHandler = xhrHandler; 657 this.conf = conf; 658 659 this.startUpdates(); 660 }; 661 FeedReader.prototype = { 662 fetchFeed: function () { 663 var that = this; 664 var callback = { 665 success: function (text, xml) { 666 that.parseFeed(text, xml); 667 }, 668 failure: function (status) { 669 that.showError(status); 670 } 671 }; 672 this.xhrHandler.request('GET', 'feedProxy.php?feed=' + this.conf.feedUrl, callback); 673 674 }, 675 parseFeed: function (responseText, responseXML) { 676 this.display.clear(); 677 var items = responseXML.getElementsByTagName('item'); 678 for (var i = 0, len = items.length; i < len; i++) { 679 var title = items[i].getElementsByTagName('title')[0]; 680 var link = items[i].getElementsByTagName('link')[0]; 681 this.display.append('<a href="' + link.firstChild.data + '">' + title.firstChild.data + '"</a>'); 682 } 683 }, 684 showError: function (statusCode) { 685 this.display.clear(); 686 this.display.append('Error fetching feed.'); 687 }, 688 stopUpdates: function () { 689 clearInterval(this.interval); 690 }, 691 startUpdates: function () { 692 this.fetchFeed(); 693 var that = this; 694 this.interval = setInterval(function () { 695 that.fetchFeed(); 696 }, this.conf.updateInterval * 1000); 697 } 698 }; 699 700 /* 把所有类和对象拼装恰来的工厂方法 */ 701 /* FeedManager namespace */ 702 var FeedManager = { 703 createFeedReader: function (conf) { 704 var displayModule = new ListDisplay(conf.id + '-display', conf.parent); 705 var simpleHandler = new SimpleHandler(); 706 var xhrHandler = simpleHandler.createXhrObject(); 707 708 return new FeedReader(displayModule, xhrHandler, conf); 709 } 710 }; 711 712 713 /* Title: Factory method 714 Description: creates objects without specifying the exact class to create 715 */ 716 var jsp = {}; 717 jsp.dom = {}; 718 jsp.dom.Text = function () { 719 this.insert = function (where) { 720 var txt = document.createTextNode(this.url); 721 where.appendChild(txt); 722 }; 723 }; 724 jsp.dom.Link = function () { 725 this.insert = function (where) { 726 var link = document.createElement('a'); 727 link.href = this.url; 728 link.appendChild(document.createTextNode(this.url)); 729 where.appendChild(link); 730 }; 731 }; 732 jsp.dom.Image = function () { 733 this.insert = function (where) { 734 var im = document.createElement('img'); 735 im.src = this.url; 736 where.appendChild(im); 737 }; 738 }; 739 jsp.dom.factory = function (type) { 740 return new jsp.dom[type]; 741 } 742 743 var o = jsp.dom.factory('Link'); 744 o.url = 'http://google.com'; 745 o.insert(document.body); 746 747 748 /* 749 工厂方法模式与IoC与DI 750 751 Ioc --- Inversion of Control, 控制反转 752 DI --- Dependency Injection, 依赖注入 753 754 1).参与者有谁? 755 一般有三方参与者,一个是某个对象(任意对象);另一个是Ioc/DI的容器(用来实现IoC/DI功能的一个框架程序);还有一个是某个对象的外部资源(对象需要的,但是是从对象外部获取的,都统称为资源)。 756 2).谁依赖于谁? 757 某个对象依赖于IoC/DI的容器 758 3).为什么需要依赖? 759 对象需要Ioc/DI的容器来提供对象需要的外部资源。 760 4).谁注入谁? 761 Ioc/DI的容器注入某个对象。 762 5).到底注入什么? 763 注入的是某个对象所需要的外部资源。 764 6).谁控制谁? 765 IoC/DI的容器来控制对象。 766 7).控制什么? 767 主要是控制对象实例的创建。 768 8).为何叫反转? 769 反转是相对于正向而言的。 770 在A类中主动去获取所需要的外部资源C,这种情况被称为正向的。反向就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向地注入到A类中。 771 9).依赖注入和控制反转是同一个概念吗? 772 依赖注入和控制反转是对同一件事情的不同描述。依赖注入是从应用程序的角度去描述。应用程序依赖容器创建并注入它所需要的外部资源。 773 而控制反转是从容器的角度去描述,容器控制应用程序,由容器反向地向应用程序注入其所需要的外部资源。 774 775 这么一个小小的改变其实是编程思想的一个大进步,有效地分离了对象和它所需要的外部资源,使它们松散耦合,有利于功能服用,更重要的是使得程序的整个体系结构变得非常灵活。 776 777 778 */ 779 780 // 用setter注入,使用Ioc/DI的示例 781 var A = function () { 782 this.c = null; 783 }; 784 A.prototype = { 785 setC: function (c) { 786 // 注入 787 this.c = c; 788 }, 789 t1: function () { 790 // 等待注入 791 this.c.tc(); 792 } 793 }; 794 795 var C = function () { 796 }; 797 C.prototype.tc = function () { 798 console.log('instance C method'); 799 }; 800 801 (new A()).setC(new C()).t1(); 802 803 // 工厂方法实现Ioc/DI 804 var A = function () { 805 }; 806 A.prototype = { 807 createC1: function () { 808 }, 809 t1: function () { 810 this.createC1().tc(); 811 } 812 }; 813 // 子类实现注入 814 var A2 = function () { 815 this.superclass.constructor.apply(this, arguments); 816 }; 817 extend(A, A2); 818 A2.prototype.createC1 = function () { 819 return new C(); 820 }; 821 822 823 // http://www.dofactory.com/javascript-factory-method-pattern.aspx 824 825 (function () { 826 function Factory() { 827 this.createEmployee = function (type) { 828 var employee; 829 830 if (type === "fulltime") { 831 employee = new FullTime(); 832 } else if (type === "parttime") { 833 employee = new PartTime(); 834 } else if (type === "temporary") { 835 employee = new Temporary(); 836 } else if (type === "contractor") { 837 employee = new Contractor(); 838 } 839 840 employee.type = type; 841 employee.say = function () { 842 log.add(this.type + ": rate " + this.hourly + "/hour"); 843 } 844 return employee; 845 } 846 847 } 848 849 var FullTime = function () { 850 this.hourly = "$12"; 851 }; 852 var PartTime = function () { 853 this.hourly = "$11"; 854 }; 855 var Temporary = function () { 856 this.hourly = "$10"; 857 }; 858 var Contractor = function () { 859 this.hourly = "$15"; 860 }; 861 862 // log helper 863 var log = (function () { 864 var log = ""; 865 return { 866 add: function (msg) { 867 log += msg + "\n"; 868 }, 869 show: function () { 870 alert(log); 871 log = ""; 872 } 873 } 874 })(); 875 876 877 function run() { 878 879 var employees = []; 880 881 var factory = new Factory(); 882 883 employees.push(factory.createEmployee("fulltime")); 884 employees.push(factory.createEmployee("parttime")); 885 employees.push(factory.createEmployee("temporary")); 886 employees.push(factory.createEmployee("contractor")); 887 888 for (var i = 0, len = employees.length; i < len; i++) { 889 employees[i].say(); 890 } 891 892 log.show(); 893 } 894 }()); 895 896 897 </script> 898 </body> 899 </html>