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         '<': '&lt;',
323         '>': '&gt;',
324         '&': '&amp;',
325         '"': '&quot;'
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>

 

posted @ 2013-05-26 18:15  LukeLin  阅读(850)  评论(0编辑  收藏  举报