读书笔记之 - javascript 设计模式 - 接口、封装和链式调用
2014-09-23 13:46 sai.zhao 阅读(844) 评论(0) 编辑 收藏 举报javascript 采用设计模式主要有下面的三方面原因:
- 可维护性:设计模式有助于降低模块之间的耦合程度。这使代码进行重构和换用不同的模块变得容易,也使程序员在大型项目中合作变得容易。
- 沟通:设计模式为处理不同类型的对象提供了一套通用的术语。程序员可以简洁的描述自己系统的工作方式。
- 性能:采用一些优化性能的模式,可以大幅度提高程序的执行效率,如享元模式和代理模式等
同时,滥用设计模式也会带来一些后果:
- 复杂性:代码变得复杂,新手难以理解
- 性能:多数设计模式会或多或少的降低代码的性能
实现容易,合理使用才是难点。一个建议就是:尽量选用最合适的那种,但不要过度的牺牲性能。
接口:接口是用来说明该对象具有哪些方法的手段,尽管其表明这些方法的语义,但是它不规定实现。
在Javascript中模仿接口
一:用注释描述接口
/* interface Composite{ function add(child); function remove(child); function getChild(index); } interface FormItem{ function save(); } */ var CompositeForm = function(id,method,action){ } CompositeForm.prototype.add = function(){ } CompositeForm.prototype.remove = function(){ } CompositeForm.prototype.getChild = function(){ } CompositeForm.prototype.save = function(){ }
这种模仿只是停留在文档阶段。没有对是否实现正确的方法进行检查,也不会抛出错误,完全依靠自觉。
二:用属性检查模仿接口:
/* interface Composite{ function add(child); function remove(child); function getChild(index); } interface FormItem{ function save(); } */ var CompositeForm = function(id,method,action){ this.implementsInterfaces = ['Composite','FormItem']; } function addForm(formInstance){ if(!implements(formInstance,'Composite','FormItem'){ throw new Error("Object does not implement a required interface."); }) } function implements(object){ for(var i=1;i<arguments.length;i++){ var interfaceName = argument[i]; var interfaceFound = false; for(var j=0;j<object.implementsInterfaces.length;j++){ if(object.implementsInterfaces[j]==interfaceName){ interfaceFound = true; break; } } //有接口没有找到 if(!interfaceFound){ return false; } } //找到了所有的接口 return true; }
这里,CompositeForm 宣称自己实现了'Composite','FormItem'这俩个接口,其做法是把这俩个接口的名称加入到一个对象的数组中,显式的声明自己支持的接口。
任何一个要求其参数属于特定类型的函数都可以对这个属性进行检查,并在找不到实现方法的时候,抛出异常。
三、鸭式辨型模仿接口
它把对象实现的方法集作为判断它是不是某个类的唯一标准,这种方法背后的观点很简单:如果对象具有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。
Interface 类的定义
/* * 定义接口方法 */ var Interface = function(name,methods){ if(arguments.length!=2){ throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2."); }; this.name = name ; this.methods = []; for (var i = 0,len = methods.length - 1; i < len; i++) { if(typeof methods[i]!=='string'){ throw new Error("Interface constructor expects method names to be " + "passed in as a string."); } this.methods.push(methods[i]); }; }; /* * 给Interface扩展静态验证方法 */ Interface.ensureImplements = function(obj){ if(arguments.length<2){ throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected at least 2."); }; for (var i = 0,len = methods.length - 1; i < len; i++) { var interface = arguments[i]; if(interface.constructor!==interface){ throw new Error("Function Interface.ensureImplements expects arguments " + "two and above to be instances of Interface."); }; for (var j = 0,methodsLen = interface.methods.length; j<methodsLen; j++) { var method = interface.methods[j]; if(!object[method]||typeof object[method] !== 'function'){ throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface. Method " + method + " was not found."); }; }; }; }
继承
链式继承的实现
function extend(subClass,superClass){ var F = function(){}; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.peototype.constructor = subClass; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor){ superClass.prototype.constructor = superClass; } }
superclass 用来弱化子类和超类之间的耦合。同时确保超类的constructor被正确设置。
原型继承的实现,实际上是拷贝继承,实现方式如下。
function clone(object){ function F(){}; F.prototype = object; return new F; }
实际上返回的是一个以给定对象为原生对象的空对象。
掺元类的实现。有一种重用的方法不需要用到严格的继承,如果想把一个函数用到多个类中,可以扩充的方式让这些类共享该函数,做法:先创建一个包含各种通用方法的类,然后再用它扩充其他的类,这种包含通用方法的类称为掺元类。
掺元类的实现方法:
function augment(receivingClass,givingClass){ //if has the third arg if(arguments.length[2]){ for(var i=2,len = arguments.length;i<len;i++){ receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; } }else{ for(methodName in givingClass.prototype){ if(!receivingClass.prototype[methodName]){ receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } } }
封装
封装就是对对象内部的数据表现形式和实现细节进行隐藏。如果想访问封装过的对象,只能使用已定义的操作这一种方式。
在javascript中,我们没有关键字,只能使用闭包的概念来创建私有的属性和方法。
javascript 想创建对象的基本模式有三种。
门户大开式,用下划线表示私有方法和属性,用闭包来创建真正的私有成员。
var Book = function(newIsbn,newTitle,newAuthor){ //私有属性 var isbn,title,anthor; function checkIsbn(isbn){ ... }; //特权方法,可以访问私有变量 this.getIsbn = function(){ return isbn; }; this.setIsbn = function(){ if(!checkIsbn(newIsbn)) throw new Error('Book:Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function(){ return newTitle; }; this.setTitle = function(newTitle){ title = newTiyle||''; }; this.getAuthor = function(){ return author; }; this.setAuthor = function(newAuthor){ author = newAuthor||''; }; this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); } //不需要直接访问私有属性的方法都可以在prototype中声明。 Book.prototype = { display:function(){...} }
更高级的对象创建。
var Book = (function(){ //私有静态属性 var numOfBooks = 0; //私有静态方法 function checkIsbn(isbn){ ... }; return function(newIsbn,newTitle,newAuthor){ //私有属性 var isbn,title,anthor; //特权方法,可以访问私有变量 this.getIsbn = function(){ return isbn; }; this.setIsbn = function(){ if(!checkIsbn(newIsbn)) throw new Error('Book:Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function(){ return newTitle; }; this.setTitle = function(newTitle){ title = newTiyle||''; }; this.getAuthor = function(){ return author; }; this.setAuthor = function(newAuthor){ author = newAuthor||''; }; numOfBooks++; if(numOfBooks > 50) throw new Err('Book:only 50 instances of Book can be created.'); this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); } })(); //不需要直接访问私有属性的方法都可以在prototype中声明。 Book.prototype = { display:function(){...} }
一些简单的说明:
- 首先,用()()创建了一个闭包,立即执行,并返回另一个函数。
- 其次,返回函数被赋值给Book,成为一个构造函数。
- 三次,实例化Book调用的内层函数,外层只是用于创建一个可以存放静态私有成员的闭包。
- 四次,checkIsbn被设计为静态方法,因此Book的每个实例都不会生成该静态方法,该方法作用在构造类上,而不是实例上。
- 五次,这些静态方法因为包含在闭包中,所以闭包中的其它方法可以访问,而且内存中只存在一份。
- 六次,这些方法声明在返回构造器之外,不是特权方法,所以不能访问定义在构造器中的私有属性。
- 七次,判断一个私有方法是否应该被设计为静态方法,一条经验法则则是看它是否要访问任何实例数据,如果不需要,那么将其设计为静态方法则更有效率。