设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。我们写代码总是会无意识的接触到别人的设计模式,例如每种语言都有自己的监听机制,这么看来其实设计模式从我们学编程起就一直伴随左右了。其实说到设计模式就离不开经验,当我们不断在一门语言里实践的过程中,总会用自己的理解组织代码。例如我每次重新写一个功能,都会总结哪里可以抽出来复用,哪里改动之后可以作为插件供其他人使用,这样就无意识的使用了一种设计模式。
使用设计模式都是为了让代码好维护,可读性更高,调用更合理。尤其是面向对象程序设计里,要遵循一些原则才具有良好的设计。
设计原则
面向对象程序设计有几个原则:单一职责原则 (Single Responsiblity Principle SRP)、里氏代换原则(Liskov Substitution Principle,LSP)、依赖倒转原则(Dependency Inversion Principle,DIP)、接口隔离原则(Interface Segregation Principle,ISP)、最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)、开闭原则(Open Closed Principle,OCP)。开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法。
我先简单介绍以下这几个原则的意思:
- 单一职责原则:一个类只负责一项职责。在JS里即一个对象只做一件事情。
- 里氏代换原则:类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
- 依赖倒转原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
- 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 最小知识原则:一个对象应该对其他对象保持最少的了解。也就是对象之间耦合度要小。
- 开闭原则:一个软件实体如类、模块和函数应该可以扩展,但不可以修改。
接下来看看我们常用的设计模式是怎么实现这些原则的。
观察者模式
这个在实践中真的是随处可见。
1 2
| dom.addEventListener("click",function(){ ...code... },false); dom.click();
|
用的不能再多了吧,这个设计有什么好处呢?
假若,我们点击不同的DOM节点要触发一些事件,而事件和节点之间本身无关联,我们需要灵活的进行订阅和取消。
1 2 3 4 5 6 7 8 9
| function callback1(){} function callback2(){} function callback3(){} dom1.addEventListener("click",callback1,false); dom1.addEventListener("click",callback3,false); dom2.addEventListener("click",callback2,false); dom2.addEventListener("click",callback3,false); dom1.click(); dom2.click();
|
dom1
并不需要关心dom2
订阅过什么,dom1
也不需要关心dom1
之前订阅过什么,这样的模式在开发中优势就很明显了。
策略模式
表单验证写过不少吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| registerForm.onsubmit = function(){ if ( registerForm.userName.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( registerForm.password.value.length < 6 ){ alert ( '密码长度不能少于6 位' ); return false; } if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){ alert ( '手机号码格式不正确' ); return false; } }
|
怎么样,现在还能看,但是当表单有100个input要一一验证怎么破?这样的话我们其实会调用一些组件库,像我正在用的组件库里其实有很多这样的判断字典:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var strategies = { isNonEmpty: function( value, errorMsg ){ if ( value === '' ){ return errorMsg ; } }, minLength: function( value, length, errorMsg ){ if ( value.length < length ){ return errorMsg; } }, isMobile: function( value, errorMsg ){ if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ return errorMsg; } } };
|
然后在webapp中我们只需要向一个验证方法添加所需要的策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var validataFunc = function(){ var validator = new Validator(); validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); validator.add( registerForm.password, 'minLength:6', '密码长度不能少于6 位' ); validator.add( registerForm.phoneNumber, 'isMobile', '手机号码格式不正确' ); var errorMsg = validator.start(); return errorMsg; } var registerForm = document.getElementById( 'registerForm' ); registerForm.onsubmit = function(){ var errorMsg = validataFunc(); if ( errorMsg ){ alert ( errorMsg ); return false; } };
|
装饰者模式
还是表单验证,这是我们提交表单时的代码,怎么样,又有表单验证,又有ajax请求,符合单一职责原则吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| registerForm.onsubmit = function(){ if ( registerForm.userName.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( registerForm.password.value.length < 6 ){ alert ( '密码长度不能少于6 位' ); return false; } if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){ alert ( '手机号码格式不正确' ); return false; } ajax(url,param); }
|
当然,我们可以用钩子来优化代码,但随着功能的扩展,也可能出现if/else
交替增加,又变成了噩梦。
那么我们可以用装饰者模式解决。
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式,比如天冷了就多穿一件外套,需要飞行时就在头上插一支竹蜻蜓,遇到一堆食尸鬼时就点开AOE(范围攻击)技能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| Function.prototype.before = function( beforefn ){ var __self = this; return function(){ if ( beforefn.apply( this, arguments ) === false ){ return; } return __self.apply( this, arguments ); } } var validata = function(){ if ( username.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( password.value === '' ){ alert ( '密码不能为空' ); return false; } } var formSubmit = function(){ var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); } formSubmit = formSubmit.before( validata ); submitBtn.onclick = function(){ formSubmit(); }
|
其实ES6新增的Promise就算是一个装饰者,不过它是后置装饰,形成了一个异步的包装链。
设计模式还有很多说不完的东西,边实践边总结吧。