B 2.6 接口和动态类型
在开发过程当中,我们有时候可能需要指定一些不需要具体实现的行为,例如被正方形,圆形等形状实现的shape对象,我们不能给他指定一个具体的形状。这个shape对象就是一个抽象的东西,并没有一个具体的实现。
一个C++的虚类,或者Java的接口就可以实现这种要求。我们经常会说接口定义了不同组件之间的约束,有了这些约束,这个shape就可以不用考虑具体的实现,而实现这个累得人也不用考虑其他库文件的内容或者这个接口的其他实现。
接口提供了事务的分离,并且支持很多设计模式,如果我们在Ajax中要用设计模式,那么我们就要用接口。JavaScript没有关于接口的通用定义,那我们应该怎么做呢?
最简单的方法就是非正式的定义这些约束,然后依赖于使用接口的程序员来知道他们在干什么。Dave Thomas给这种方法起过一个名字“动态类型”,就是说如果我们像鸭子一样走路,并且像鸭子一样摇摆,那类似于shape接口的就是一个鸭子,如果我们能够计算面积和周长的话,那就是一个形状。
现在假设我们要将两种shape的面积相加,用Java,我们可以这样写:
public double addAreas(Shape s1, Shape s2){
return s1.getArea()+s2.getArea();
}
这种写法防止了我们将一个非shape作为参数传入,所以在方法体内,我们肯定是遵守了约束。但是在JavaScript中,方法的变量并没有具体类型,所以也就没有这种保护了:
function addAreas(s1,s2){
return s1.getArea()+s2.getArea();
}
如果这两个对象当中的任意一个没有getArea()这个函数,我们就会得到一个JavaScript错误,我们可以象下面这样在调用之前先进行检查:
function hasArea(obj){
return obj && obj.getArea && obj.getArea instanceof Function;
}
然后修改函数以便能用上这个检查:
function addAreas(s1,s2){
var total=null;
if (hasArea(s1) && hasArea(s2)){
total=s1.getArea()+s2.getArea();
}
return total;
}
通过JavaScript的反射机制,我们可以写一个函数用来检查一个对象中是否有某个指定函数:
function implements(obj,funcName){
return obj && obj[funcName] && obj[funcName] instanceof Function;
}
或者,我们也可以把它增加到Object类的原型:
Object.prototype.implements=function(funcName){
return this && this[funcName] && this[funcName] instanceof Function;
}
这样我们就可以通过名字来检验是否存在指定的函数:
function hasArea(obj){
return obj.implements("getArea");
}
或者直接通过接口来测试:
function isShape(obj){
return obj.implements("getArea") && obj.implements("getPerimeter");
}
这样虽然不如Java中那样安全,但是至少也安全了一些。可能有的对象要实现getArea()来返回一个字符型的返回值,而不是数字型,但是在JavaScript中如果不调用这个函数,我们是没有办法知道它的返回值类型的,因为JavaScript没有预定义的类型。不过可以写一系列检验的函数,例如:
function isNum(arg){
return parseFloat(arg)!= NaN ;
}
NaN是“not a number”的缩写,一个用来处理数字格式错误的JavaScript变量,如果arg以数字开头,这个函数就会返回true,实际上,parseFloat()和他的兄弟parseInt()会尽力提取可以认识的数字,比方说parseFloat(“64 hectares”)转换成64,而不是NaN.
下面就进一步来完善addAreas()这个函数:
function addAreas(s1,s2){
var total=null;
if (hasArea(s1) && hasArea(s2)){
var a1=s1.getArea();
var a2=s2.getArea();
if (isNum(a1) && isNum(a2)){
total=parseFloat(a1)+parseFloat(a2);
}
}
return total;
}
在这里,我调用了parseInt()来处理字符串型的变量。如果s1返回32,s2返回64,那么addAreas()将会返回96,如果没有使用parseFloat的话,可能就会得到3264了。
总之,用动态类型可以很简单,但是必须要在整个团队内保持一致,在灵活的Ruby模式中,动态类型很受欢迎,如果一个人或者一个小团队转到一个大的工程,而且包括分组的话,这种类型的功能就要弱一些了,如果你想给动态类型家一些检验和平衡的话,这部分所讲的就是你应该做的。
我们从对象的角度研究了语言,现在就从函数的角度来分析一下,看看他们到底是什么。