接口是面向对象JavaScript程序员的工具箱中最有用的工具之一。在设计模式中提出的可重用的面向对象设计的原则之一就是“针对接口编程而不是实现编程”,即我们所说的面向接口编程,这个概念的重要性可见一斑。但问题在于,在JavaScript的世界中,没有内置的创建或实现接口的方法,也没有可以判断一个对象是否实现了与另一个对象相同的一套方法,这使得对象之间很难互换使用,好在JavaScript拥有出色的灵活性,这使得模拟传统面向对象的接口,添加这些特性并非难事。接口提供了一种用以说明一个对象应该具有哪些方法的手段,尽管它可以表明这些方法的含义,但是却不包含具体实现。有了这个工具,就能按对象提供的特性对它们进行分组。例如,假如A和B以及接口I,即便A对象和B对象有极大的差异,只要他们都实现了I接口,那么在A.I(B)方法中就可以互换使用A和B,如B.I(A)。还可以使用接口开发不同的类的共同性。如果把原本要求以一个特定的类为参数的函数改为要求以一个特定的接口为参数的函数,那么所有实现了该接口的对象都可以作为参数传递给它,这样一来,彼此不相关的对象也可以被相同地对待。
二、接口的利与弊既定的接口具有自我描述性,并能够促进代码的重用性,接口可以提供一种信息,告诉外部一个类需要实现哪些方法。还有助于稳定不同类之间的通信方式,减少了继承两个对象的过程中出现的问题。这对于调试也是有帮助的,在JavaScript这种弱类型语言中,类型不匹配很难追踪,使用接口时,如果出现了问题,会有更明确的错误提示信息。当然接口并非完全没有缺点,如果大量使用接口会一定程度上弱化其作为弱类型语言的灵活性,另一方面,JavaScript并没有对接口的内置的支持,只是对传统的面向对象的接口进行模拟,这会使本身较为灵活的JavaScript变得更加难以驾驭。此外,任何实现接口的方式都会对性能造成影响,某种程度上归咎于额外的方法调用开销。接口使用的最大的问题在于,JavaScript不像是其他的强类型语言,如果不遵守接口的约定,就会编译失败,其灵活性可以有效地避开上述问题,如果是在协同开发的环境下,其接口很有可能被破坏而不会产生任何错误,也就是不可控性。
在面向对象的语言中,使用接口的方式大体相似。接口中包含的信息说明了类需要实现的方法以及这些方法的签名。类的定义必须明确地声明它们实现了这些接口,否则是不会编译通过的。显然在JavaScript中我们不能如法炮制,因为不存在interface和implement关键字,也不会在运行时对接口是否遵循约定进行检查,但是我们可以通过辅助方法和显式地检查模仿出其大部分特性。
三、在JavaScript中模仿接口在JavaScript中模仿接口主要有三种方式:通过注释、属性检查和鸭式辩型法,以上三种方式有效结合,就会产生类似接口的效果。
注释是一种比较直观地把与接口相关的关键字(如interface、implement等)与JavaScript代码一同放在注释中来模拟接口,这是最简单的方法,但是效果最差。
/*******************************************************************************************************************************************************************************************************************/
/**
* 创建接口对象
* @param name 接口名
* @param methods 接口方法
*/
var
Interface =
function
(name,methods){
if
(arguments.length != 2){
throw
new
Error(
'必须输入两个参数,当前个数'
+arguments.length);
}
this
.name=name;
this
.methods=[];
for
(
var
i=0, len=methods.length; i<len; i++){
if
(
typeof
methods[i] !==
'string'
){
throw
new
Error(
'方法名参数必须为string'
);
}
this
.methods.push(methods[i]);
}
};
/**
* 接口实现
* @param object1 实现接口对象
* @param object2 对应接口
* @return 实现错误抛出异常
*/
Interface.ensureImplements =
function
(object){
if
(arguments.length < 2){
throw
new
Error(
'必须输入两个参数,当前个数'
+ arguments.length);
}
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(
"接口名:"
+interface.name+
"方法名:"
+method+
"没找到"
);
}
};
}
}
var
DynamicMap =
new
Interface(
'DynamicMap'
,[
'centerOnPoint'
,
'zoom'
,
'draw'
]);
/**
* 执行方法
* @param 函数方法
* @return 执行结果
*/
function
displayRoute(mapInstance){
Interface.ensureImplements(mapInstance,DynamicMap);
//实现接口
/**
* 调用
*/
mapInstance.centerOnPoint(12,34);
mapInstance.zoom(5);
mapInstance.draw();
}
/**
* 函数方法
* @type 实现接口方法
*/
var
a={
centerOnPoint:
function
(x,y){
console.log(x*y);
},
zoom:
function
(x){
console.log(x);
},
draw:
function
(){
console.log(
"x*y"
);
}
}
displayRoute(a);