javascript设计模式-策略模式(Strategy)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title></title> 5 <meta charset="UTF-8"/> 6 </head> 7 <body> 8 <img src="1.jpg" alt=""/> 9 10 <script> 11 /* 12 设计原则 13 14 1.找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。 15 16 把会变化的部分取出并“封装”起来,好让其他部分不会受到影响。代码变化引起的不经意后果变少,系统变得更有弹性。 17 18 几乎是每个设计模式的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。 19 20 21 2.针对接口编程,而不是针对实现编程。 22 23 针对接口编程真正的意思是“针对超类型(supertype)编程”。 24 变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型 25 26 27 3.多用组合,少用继承。 28 。 29 */ 30 31 /** 32 * 策略模式 33 * 34 * 定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 35 * 36 * 本质: 37 * 分离算法,选择实现。 38 * 39 * 策略模式的重心不是如何实现算法,而是如何组织,调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。 40 * 41 * 这里鸭子的行为即是算法族。 42 * 43 * 为了实现让算法能独立于使用它的客户,策略模式引入了一个上下文对象,这个对象负责持有算法,但是不负责绝对具体选用哪个算法,把选择算法的功能交给了客户,由客户选择好具体的算法后,设置到上下文对象中,让上下文对象持有客户选择的算法。当客户通知上下文对象执行功能的时候,上下文对象则转调具体的算法。这样一来,具体的算法和直接使用算法的客户是分离的。 44 * 具体的算法和使用它的客户分离以后,使得算法可独立于使用它的客户而变化,并且能够动态地切换需要使用的算法,只要客户端动态地选择使用不同的算法,然后设置到上下文对象中,在实际调用的时候,就可以调用到不同的算法。 45 * 46 * 1.功能 47 * 策略模式的功能是把具体的算法实现从具体的业务处理中独立出来,把它们实现成为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换。 48 * 策略模式的中心不是如何来实现算法,而是如何组织,调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。 49 * 50 * 2.策略模式和if-else语句 51 * 多个if-else语句表达的就是一个平等的功能结构。而策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文来与具体的策略类进行交互。 52 * 因此多个if-else语句可以考虑使用策略模式。 53 * 54 * 3.算法的平等性 55 * 策略模式的很大的特点就是各个策略算法的平等性。所有策略算法在实现上也是相互独立的,相互之间没有依赖的。 56 * 所以策略算法是相同行为的不同实现。 57 * 58 * 4.谁来选择具体的策略算法 59 * 一个是在客户端,当使用上下文的时候,由客户端来选择具体的策略算法,然后把这个策略算法设置给上下文。 60 * 另一个是由上下文来选择具体的策略算法。 61 * 62 * 5.运行时策略的唯一性 63 * 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但使用时只能使用一个。 64 * 65 * 6.增加新的策略 66 * 策略模式可以让你很灵活地扩展新的算法。 67 * 68 * 69 * 策略模式的调用顺序 70 * 1.先是客户端来选择并创建具体的策略对象。 71 * 2.然后客户端创建上下文。 72 * 3.接下来客户端就可以调用上下文的方法来执行功能了,在调用的时候,从客户端传入算法需要的参数。 73 * 4.上下文接到客户的调用请求,会把这个转发给它持有的Strategy。 74 * 75 * 76 * Context和Strategy的关系 77 * 通常是上下文使用具体的策略实现对象,反过来,策略实现对象也可以从山下问获取所需要的数据。 78 * 79 * 80 * 策略模式结合模板方法模式 81 * 对于一系列算法的实现上存在公共功能的情况,策略模式可以有以下三种实现方式: 82 * 1.在上下文当中实现公共功能,让所有具体的策略算法回调这些方法。 83 * 2.将策略的借口改成抽象类,然后在其中实现具体算法的公共功能。 84 * 3.为所有的策略算法定义一个抽象的父类,让这个父类去实现策略的接口,然后在这个父类中去实现公共的功能。 85 * 如果这个时候发现一系列算法的实现步骤都是一样的,只是在某些局部步骤上有所不用的情况,那就可以在这个抽象类里面定义算法实现的骨架,然后让具体的策略算法去实现变化的部分。这样的一个结构自然就变成策略模式结合模板方法模式了。那个抽象类就成了模板方法模式的模板类。 86 */ 87 88 (function () { 89 // 示例代码 90 91 // 具体算法 92 function ConcreteStrategyA() {} 93 94 ConcreteStrategyA.prototype.algorithmInterface = function () {/*具体算法的实现*/}; 95 96 function ConcreteStrategyB() {} 97 98 ConcreteStrategyB.prototype.algorithmInterface = function () {}; 99 100 function ConcreteStrategyC() {} 101 102 ConcreteStrategyC.prototype.algorithmInterface = function () {}; 103 104 // 上下文对象,通常会持有一个具体的策略对象 105 function Context(strategy) { 106 this.strategy = strategy; 107 } 108 109 //上下文对客户端提供的操作接口,可以有参数和返回值 110 Context.prototype.contextInterface = function () { 111 // 转调具体的策略对象进行算法运算 112 this.strategy.algorithmInterface(); 113 }; 114 115 }()); 116 117 (function () { 118 // 容错恢复机制 119 function DbLog() { 120 this.log = function (msg) { 121 if (msg && msg.trim().length > 5) { 122 fds; // make mistake 123 } 124 console.log('现在把' + msg + '记录到数据库中'); 125 }; 126 } 127 128 function FileLog() { 129 this.log = function (msg) { 130 console.log('现在把' + msg + '记录到文件中'); 131 }; 132 } 133 134 function LogContext() { 135 this.log = function (msg) { 136 var strategy = new DbLog(); 137 try { 138 strategy.log(msg); 139 } catch (e) { 140 strategy = new FileLog(); 141 strategy.log(msg); 142 } 143 }; 144 } 145 146 var log = new LogContext(); 147 log.log('"记录日志"'); 148 log.log('"再次记录日志"'); 149 150 }()); 151 152 153 /* 154 适用性 155 156 1.许多相关的类仅仅是行为有异。“策略”提供了一种同多个行为中的一个行为来配置一个类的方法。 157 2. 158 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。 159 3.算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的,与算法相关的数据结构。 160 4.一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。 161 */ 162 163 /* 164 优点: 165 166 1.定义了一系列算法。 167 2.避免多重条件语句。 168 3.更好的扩展性。 169 170 缺点: 171 172 1.客户必须了解每种策略的不同。 173 2.增加了对象数目。 174 3.只适合扁平的算法结构。 175 对于出现需要嵌套使用多个算法的情况,可以考虑使用装饰者模式,或是变形的职责链模式。 176 177 178 相关模式 179 策略模式和状态模式 180 从模式结构上看是一样的,但是实现的功能确实不一样的。 181 状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类,每个状态对应的类实现了该状态对应的功能,在实现功能的同时,还会维护状态数据的变化。这些实现状态对应的功能的类之间是不能相互替换的。策略模式是根据需要或者是客户端的要求来选择相应的实现类,各个实现类是平等的,是可以相互替换的。 182 另外策略模式可以让客户端来选择需要使用的策略算法;而状态模式一般是上下文,或者是在状态实现类里面来维护具体的状态数据们通常不由客户端来制定状态。 183 184 策略模式和模板方法模式 185 可组合使用。模板方法重在封装算法骨架,而策略模式重在分离并封装算法实现。 186 187 策略模式和享元模式 188 可组合使用。 189 190 */ 191 192 193 (function () { 194 // 抽象上下文类 195 var Duck = function (quackBehavior) { 196 this.quackBehavior = quackBehavior; 197 }; 198 Duck.prototype = { 199 display: function () { 200 throw new Error('This is abstract method'); 201 }, 202 performQuack: function () { 203 this.quackBehavior.quack(); 204 }, 205 swim: function () { 206 console.log('All ducks float, even decoys'); 207 } 208 }; 209 210 // 具体上下文类 211 var MallardDuck = function () { 212 Duck.apply(this, arguments); 213 }; 214 MallardDuck.prototype.__proto__ = Duck.prototype; 215 MallardDuck.prototype.display = function () { 216 console.log('I\'m a real Mallard duck.'); 217 }; 218 219 // 抽象算法类 220 var QuackBehavior = function () { 221 }; 222 QuackBehavior.prototype.quack = function () { 223 throw new Error('this is an interface'); 224 }; 225 226 // 具体算法类 227 var MuteQuack = function () { 228 }; 229 MuteQuack.prototype.__proto__ = QuackBehavior.prototype; 230 MuteQuack.prototype.quack = function () { 231 console.log('slience'); 232 }; 233 234 // 客户端 235 var MiniDucksSimulator = function () { 236 this.main(); 237 }; 238 MiniDucksSimulator.prototype = { 239 main: function () { 240 var mallard = new MallardDuck(new MuteQuack()); 241 mallard.performQuack(); 242 } 243 }; 244 new MiniDucksSimulator(); 245 246 // 动态设定行为 247 Duck.prototype.setQuackBehavior = function (qb) { 248 this.quackBehavior = qb; 249 }; 250 251 var ModelDuck = function () { 252 Duck.apply(this, arguments); 253 }; 254 ModelDuck.prototype.__proto__ = Duck.prototype; 255 ModelDuck.prototype.display = function () { 256 console.log('T\'m a model duck.'); 257 }; 258 259 var QuackSpeakerPowered = function () { 260 }; 261 QuackSpeakerPowered.prototype.quack = function () { 262 console.log('I\'m quacking with a speaker'); 263 }; 264 265 MiniDucksSimulator.prototype.main = function () { 266 var model = new ModelDuck(new MuteQuack()); 267 model.performQuack(); 268 model.setQuackBehavior(new QuackSpeakerPowered()); 269 model.performQuack(); 270 }; 271 new MiniDucksSimulator(); 272 273 274 // 策略,定义计算报价算法的接口 275 var Strategy = function () { 276 }; 277 Strategy.prototype.calcPrice = function (goodsPrice) { 278 throw new Error('This is an abstract interface'); 279 }; 280 281 var NormalCustomerStrategy = function () { 282 }; 283 NormalCustomerStrategy.prototype.__proto__ = Strategy.prototype; 284 NormalCustomerStrategy.prototype.calcPrice = function (goodsPrice) { 285 console.log('对于新客户或者普通客户,没有折扣'); 286 return goodsPrice; 287 }; 288 289 // 具体算法实现 290 var OldCustomerStrategy = function () { 291 }; 292 OldCustomerStrategy.prototype.__proto__ = Strategy.prototype; 293 OldCustomerStrategy.prototype.calcPrice = function (goodsPrice) { 294 console.log('对于老客户,统一折扣5%'); 295 return goodsPrice * (1 - 0.05); 296 }; 297 298 // 价格管理,主要完成计算向客户所报价格的功能 299 var Price = function (strategy) { 300 this.strategy = strategy; 301 }; 302 Price.prototype.quote = function (goodsPrice) { 303 return this.strategy.calcPrice(goodsPrice); 304 }; 305 306 // 选择并创建需要使用的策略对象 307 var strategy = new OldCustomerStrategy(); 308 // 创建上下文 309 var ctx = new Price(strategy); 310 // 计算报价 311 var quote = ctx.quote(1000); 312 console.log('向客户报价:' + quote); 313 }()); 314 315 316 (function () { 317 // 表单验证 318 var r_space = /\s+/; 319 320 // HTML转义 321 var ENCODECHAR = { 322 '<': '<', 323 '>': '>', 324 '&': '&', 325 '"': '"' 326 }; 327 328 // 验证策略 329 var VALIDTYPES = { 330 'nonEmpty': { 331 validate: function (value) { 332 return value !== ''; 333 }, 334 msg: '此项不能为空' 335 }, 336 'email': { 337 validate: function (value) { 338 return (/^[\w\-]+@[\w\-]+(?:\.[\w\-]+)+$/.test(value)); 339 }, 340 msg: function (value) { 341 return (value ? '请输入正确格式的邮箱' : '请输入你的邮箱'); 342 } 343 }, 344 'phone': { 345 validate: function (value) { 346 return (/^1[3458]\d{9}$/.test(value)); 347 }, 348 msg: function (value) { 349 return (value ? '请输入正确格式的手机号码' : '请输入你的手机号码'); 350 } 351 } 352 }; 353 354 var formHooks = { 355 'radio': 'checked', 356 'checkbox': 'checked' 357 }; 358 359 var formEventsHooks = { 360 'text': formEventsGetter('blur'), 361 'textarea': formEventsGetter('blur'), 362 'checkbox': formEventsGetter('click'), 363 'select': formEventsGetter('change'), 364 'radio': formEventsGetter('click') 365 }; 366 367 function formEventsGetter(type) { 368 return function (el, context, item) { 369 $(el).on(type, function () { 370 context.errHandler = []; 371 parseEachEleCfg(item); 372 373 validating(item, context.errHandler); 374 375 context.handleError(); 376 }); 377 }; 378 } 379 380 /** 381 * 验证器构造器 382 * @param {Object} formInstance 用户自定义规则 383 * @constructor 384 */ 385 function Validator(formInstance) { 386 var form = formInstance.form; 387 if (!form) return; 388 389 this.form = form; 390 391 /** 392 [{ 393 elem:elem, 394 value: '', 395 type: '' 396 [optional] ,checker: {checker: func, description: ''} 397 }, ..] 398 */ 399 this.config = []; 400 401 this.callbackLists = { 402 success: [], 403 failure: [] 404 }; 405 406 /* 407 this.errHandler; 408 */ 409 410 if (formInstance.types) $.extend(VALIDTYPES, formInstance.types); 411 412 this.parsed = false; 413 this.isDefaultPrevented = false; 414 this.ajax = typeof formInstance.ajax === 'boolean' ? 415 formInstance.ajax : true; 416 417 if (formInstance.success) this.on('success', formInstance.success); 418 if (formInstance.failure) this.on('failure', formInstance.failure); 419 if (formInstance.beforeSend) this.beforeSend = formInstance.beforeSend; 420 421 if (formInstance.formElementsEvented) { 422 this.parseConfig(); 423 this.parsed = true; 424 this.addFormEvents(this.config); 425 } 426 427 var removeClassFn = function (e) { 428 $(e.target).removeClass('processing'); 429 }; 430 this.on('success', removeClassFn); 431 this.on('failure', removeClassFn); 432 433 this.submit(); 434 } 435 436 // 防止XSS 437 Validator.encodeValue = function (value) { 438 for (var i in ENCODECHAR) { 439 if (ENCODECHAR.hasOwnProperty(i)) 440 value = value.replace(new RegExp(i, 'g'), ENCODECHAR[i]); 441 } 442 443 return value; 444 }; 445 446 Validator.prototype = { 447 // 为每个表单元素添加事件侦听 448 addFormEvents: function (cfg) { 449 var me = this; 450 var elem, formType, item; 451 for (var i = 0, len = cfg.length; i < len; i++) { 452 item = cfg[i]; 453 elem = item.elem; 454 formType = elem.type; 455 456 formEventsHooks[formType](elem, me, item); 457 } 458 }, 459 hasErrors: function () { 460 return !!this.errHandler.length; 461 }, 462 on: function (type, cb) { 463 if (!this.callbackLists[type]) { 464 throw new Error('no matched event type'); 465 } 466 467 this.callbackLists[type] = this.callbackLists[type].concat( 468 Object.prototype.toString.call(cb) === '[object Array]' ? 469 cb : [cb] 470 ); 471 }, 472 off: function (type) { 473 if (!this.callbackLists[type]) return; 474 475 delete this.callbackLists[type]; 476 }, 477 emit: function (type, args) { 478 if (!this.callbackLists[type]) { 479 throw new Error('no matched event type'); 480 } 481 482 var list = this.callbackLists[type]; 483 484 if (type === 'failure' && args && args[0] && args[0].preventDefault) { 485 args[0].preventDefault(); 486 } 487 488 for (var i = 0, len = list.length; i < len; i++) { 489 if (typeof list[i] === 'function' && list[i].apply(this.form, args) === false) 490 break; 491 } 492 }, 493 submit: function () { 494 var me = this; 495 496 if (!this.form) return; 497 498 $(this.form).on('submit', function (e) { 499 var $this = $(this); 500 501 if ($this.hasClass('processing')) return; 502 503 $this.addClass('processing'); 504 505 me.isDefaultPrevented = false; 506 e._preventDefault = e.preventDefault; 507 e.preventDefault = function () { 508 e._preventDefault(); 509 me.isDefaultPrevented = true; 510 }; 511 512 // 解析配置,parsed为false时,可再次解析 513 if (!me.parsed) { 514 me.parseConfig(); 515 me.parsed = true; 516 } 517 518 // 验证 519 me.validate(); 520 521 // 验证有错误 522 if (me.hasErrors()) { 523 me.handleError(); 524 525 me.emit('failure', [e]); 526 return; 527 } 528 529 // ajax提交默认阻止表单提交 530 if (me.ajax) { 531 e._preventDefault(); 532 } 533 534 var def; 535 var form = this; 536 537 /* 538 执行me.beforeSend方法,在成功,提交之前执行, 539 如果返回false就触发失败回调 540 可以返回deferred对象,进行异步操作 541 */ 542 if (me.beforeSend && (def = me.beforeSend()) === false) { 543 K.handyWarn({ 544 msg: me.beforeSend.errorMsg 545 }); 546 547 me.emit('failure', [e]); 548 return; 549 } 550 551 // 如果是deferred对象,序列执行回调 552 if (def && (typeof def.pipe === 'function' || typeof def.then === 'function')) { 553 def = def.pipe || def.then; 554 // 因为是异步操作,必须阻止默认表单提交,与异步提交表单不同 555 if (!e.isDefaultPrevented()) e._preventDefault(); 556 557 return def(function () { 558 me.isDefaultPrevented = false; 559 me.emit('success', [e]); 560 // 提交表单 561 if (!me.isDefaultPrevented && !me.ajax) form.submit(); 562 }, function () { 563 me.emit('failure', [e]); 564 }); 565 } else { 566 me.emit('success', [e]); 567 } 568 }); 569 }, 570 validate: function () { 571 /** 572 [{ 573 elem: elem, 574 msg: '' 575 }, ...] 576 */ 577 this.errHandler = []; 578 579 var item; 580 581 // 遍历配置项 582 for (var i = 0, len = this.config.length; i < len; i++) { 583 item = this.config[i]; 584 585 if (parseEachEleCfg(item) === false) continue; 586 587 validating(item, this.errHandler); 588 } 589 }, 590 // 解析HTML标签中的“data-valid”属性,将有的保存 591 parseConfig: function () { 592 var elems = $('*[data-valid]:not([disabled]):not([readonly])', this.form); 593 var elem, ruler; 594 595 for (var i = 0, len = elems.length; i < len; i++) { 596 elem = elems[i]; 597 ruler = elem.getAttribute('data-valid'); 598 599 if (ruler) 600 this.config.push({ 601 elem: elem, 602 type: ruler 603 }); 604 } 605 }, 606 // 处理错误 607 handleError: function () { 608 var errs = this.errHandler; 609 610 if (errs.length) { 611 var head = errs.shift(); 612 var elem = head.elem; 613 614 K.handyWarn({ 615 msg: head.msg, 616 rel: elem, 617 relPos: 'right' 618 }); 619 620 if (elem.value) { 621 elem.select(); 622 } else { 623 elem.focus(); 624 } 625 } 626 } 627 }; 628 629 // 验证值,如果不符则保存到错误队列中 630 function validating(item, errHandler) { 631 var checkers = item.checker; 632 var description, checker, value, args, elem; 633 634 for (var i = 0, len = checkers.length; i < len; i++) { 635 checker = checkers[i].checker; 636 description = checkers[i].description; 637 elem = item.elem; 638 639 value = elem[formHooks[elem.type.toLowerCase()] || 'value']; 640 641 // fix IE用value兼容HTML5的placeholder 642 if (elem.getAttribute('placeholder') === value) { 643 value = ''; 644 } 645 646 if (value && typeof value === 'string') { 647 value = Validator.encodeValue(value); 648 } 649 650 args = [value].concat(description); 651 652 if (!checker.validate.apply(elem, args)) { 653 errHandler.push({ 654 elem: elem, 655 msg: typeof checker.msg === 'function' ? checker.msg.apply(elem, args) : checker.msg 656 }); 657 } 658 } 659 660 elem = null; 661 } 662 663 var r_brackets = /^([\w-]+)(?:\(([^)]+)\)|)$/; 664 665 function parseEachEleCfg(item) { 666 if (!(item.checker && item.checker.length)) { 667 var type, description, checker; 668 var types = item.type && item.type.split(r_space) || []; 669 670 if (!types.length) return false; 671 672 // 单个元素可以有多个checker,以空格分隔,且单个checker可有相应的描述语 673 // “charLen(24)”, 括号里面是描述语, 674 // 描述语用在错误信息中 675 item.checker = []; 676 for (var i = 0, len = types.length; i < len; i++) { 677 type = types[i].match(r_brackets); 678 if (!type) continue; 679 checker = VALIDTYPES[type[1]]; 680 description = type[2] && type[2].split(',') || []; 681 682 if (!checker) { 683 __console.error('没有相应的验证规则:' + type); 684 continue; 685 } 686 687 item.checker.push({ 688 checker: checker, 689 description: description 690 }); 691 } 692 } 693 694 return true; 695 } 696 }()); 697 </script> 698 </body> 699 </html>