深入js的面向对象学习篇——温故知新(一)
在学习设计模式前必须要知道和掌握的***。
为类添加新方法:
Function.prototype.method = function(name,fn) { this.prototype[name] = fn; } //改进版:支持链式调用 Function.prototype.method = function(name,fn) { this.prototype[name] = fn; return this; }
函数是一等对象,可以存储在变量中,可以作为参数传给其他函数,可以作为返回值从其他函数传出,还可以再运行的时候进行构造。这正是用以构造传统面向对象框架的基础。
对象具有易变性。类方法可以在类实例创建之后再添加,实例仍然能够获得新添加的方法。个人觉得,这也是对象扩展的威力源泉。
一、接口
“针对接口,而不是实现编程”
接口提供了一种用以说明一个对象应该具备哪些方法的手段。既定的一批接口具有自我描述性,并能促进代码的重用。接口可以告诉程序员一个类实现了哪些方法,从而帮助其使用这个类。接口最实在的用途是什么?~一个程序员可以针对所需要的类定义一个接口,并转交给另一个程序员。第二个程序员可以随心所欲地编写自己的代码,只要他定义的类实现了那个接口就行。
当然接口也有弊端。比如额外的方法调用的开销无法避免。
而接口使用中的最大问题在于,无法强迫其他程序员遵守你定义的接口。不像其它语言(像java等,编译期间会因为接口继承后没有写实现而报错),javascript中必须用手工的办法保证某个类实现了一个接口(你可能对此不理解是什么意思,稍安,等下立马解释^_^)。如果项目的其他程序员不认真对待接口,那么这些接口的使用是无法得到强制性保证的。除非项目的所有人都同意使用接口并对其进行检查,否则接口的很多价值都无从体现。
解惑的时候到了,为什么前文说“javascript中必须用手工的办法保证某个类实现了一个接口”。事实上,javascript没有interface和implements关键字,也不在运行时对接口约定是否得到遵守进行检查。我们只能在javascript中模仿接口。一下介绍模仿接口的三种方法:注释法、属性检查法、鸭式辨型法。
① 注释法:
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function sava(); } */ var compositeForm = function (id,method,action) { .... }; compositeForm.prototype.add = function(child) { .... }; compositeForm.prototype.remove = function(child) { ..... };
这种方法易于实现,但是不会提供错误信息。
② 用属性检查模仿接口
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function sava(); } */ var compositeForm = function(id,method,action) { this.implementInterfaces = ['Composite','FormItem']; ...... }; function addForm(formInstance) { if(!implements(formInstance,'Composite','FormItem')) { throw new Error("Object does not implement a required interface."); .... } } function implement(object) { //检查接口是否被实现 for(var i = 0;i < arguments.length;i++) { var interfaceName = arguments[i]; var interfaceFound = false; for(var j = 0;j < object.implementsInterfaces.length;j++) { if(interfaceName == object.implementsInterfaces[j]) { interfaceFound = true; break; } } if(!interfaceFound) { return false; } } return true; }
优点:它对所有实现的接口提供了文档说明。如果需要的接口不在一个类宣称支持的接口之列,你会看到错误消息。通过利用这些错误,你可以强迫其它程序员声明这些接口。
缺点:它并未确保类真正实现自称实现的接口。很可能,只是有了这个接口的实现声明,方法体忘记写了,一个空的方法体造成了假象。
③ 用鸭式辨型模仿接口
思想:类是否声明自己支持哪些接口不重要,只要它具有这些接口中的方法就行。“像鸭子一样走路并且嘎嘎叫的就是鸭子”。如果一个对象具有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。(个人觉得,这有点像客户端检测机制,我们不是去判断浏览器的类型,而是转而判断浏览器是否支持此项功能^_^换个角度思考问题往往有事半功倍之效。)
缺点:类并不声明自己实现了哪些接口,这降低了代码的可重用性,并且也缺乏其他两种方法的自我描述性。
个人觉得,最好的就接口实现时方法一和方法三的结合。也就是可读性和保证性相结合。
var Composite = new Interface('Composite',['add','remove','getChild']); var FormItem = new Interface('FormItem',['save']); var CompositeForm = function(id,method,action) { .... }; function addForm(formInstance) { Interface.ensureImplements(formInstance,Composite,FormItem); .... } var Interface = function(name,methods) { if(arguments.length != 2) { throw new Error("..........."); } this.name = name; this.methods = []; for(var i = 0;i < methods.length;i++) { if(typeof methods[i] !== 'string') { throw new Error(".........."); } this.methods.push(methods[i]); } }; Interface.ensureImplements = function() { if(arguments.length < 2) { throw new Error("...."); } for(var i = 1,len = arguments.length;i < len;i++) { var interface = arguments[i]; if(interface.constructor !== Interface) { throw new Error("...."); } 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("...."); } } } }
接口在运用设计模式实现复杂系统的时候最能体现其价值。降低了对象间的耦合程度。在大型项目中,易于程序员的合作,如果你知道测试中的桩测试,你对此用个空方法体进行占位,提供API,无疑会使程序员间的合作开发更便捷。