1. javascript接口简介
在javascript中没有内置的创建或实现接口的方法,同样也没有内置的方法可以用于判断一个对象是否实现了与一个对象相同的一套方法,这使对象很难互换使用。由于javascript的灵活性,添加这些特性并非难事。 关于什么是接口,为什么需要接口,其他内置语言的接口实现方式,这里不多说了,此类文章着重专注于传统的设计模式在javascript中的应用。
2. 在javascript中模仿接口
首先,我们可以定义一个公共的接口类: Interface,接着我们考虑下接口类需要有哪些成员和方法。
1)接口是一组方法签名的集合,其他内置接口的语言可以在接口中进行函数声明,从而定义一个接口;而在javascript中,我们需要通过给Interface类增加一个数组成员,保存方法名称,我命名为:methods,另外还有个成员:name,这个成员是接口名,方便我们快速定位错误—比如我们的对象到底是没实现哪个接口的哪个方法。
2) 在有内置接口的语言中,若一个类继承了某个接口,而未实现其中的一个或多个方法时,编译器会报错,从而提醒开发人员,但是javascript是无法提供这个功能的,所以在我们的Interface类中需要一个方法来保证在未实现某接口的所有方法时,抛出一个错误。 这个方法我们可以命名为:ensureImplents, 另外这个方法是可通用的,所以可以作为一个静态方法,即Interface的方法,而不需要在其每个实例中保存。
3.接口的实现
有了第2步的铺垫,接下来我们可以开始实现了:
/*
* 接口类构造函数,接受2个以上的参数,其中第一个参数为接口名,后面的参数可以为字符串数组,也可以为字符串
* @param {Object} name
* 接口名
* @param {Object} methods
* 接口包含的方法集合,参数可以为数组,也可以传入任意多的字符串形式的方法名
*/
var Interface = function(name, methods){
if(arguments.length < 2){ //若参数个数不为2,则抛出错误
throw new Error("Interface constructor called with" + arguments.length +
"arguments, but expected at least 2");
}
this.name = name;
this.methods = [];
for(var i = 1, len = arguments.length; i < len; ++i){
if(arguments[i] instanceof Array){ //若参数为数组,则遍历该参数
for(var j = arguments[i].length - 1; j > -1; --j){
if(typeof arguments[i][j] !== 'string' ){//保证传入的方法名为字符串,否则抛出错误
throw new Error('Interface constructor expects method names to be passed in as a string');
}
this.methods.push(arguments[i][j]); //保存方法名
}
} else if(typeof arguments[i] === 'string'){ //参数为字符串,直接保存
this.methods.push(arguments[i]);
} else { //否则抛出错误
throw new Error('Interface constructor expects method names to be passed in as a string');
}
}
};
/*
* 接口实现检验函数,第一个参数为要检查的对象,后面的任意参数为实现的接口对象,也可以为接口对象数组
* @param {Object} object
*/
Interface.ensureImplents = function(object){
if(arguments.length < 2){
throw new Error("Interface constructor called with" + arguments.length +
"arguments, but expected at least 2");
}
var _checkMethods = function(inface){ //内部函数,用于检验对象是否实现了ifs对象中的方法
var methods = inface.methods,
i = methods.length - 1;
for( ; i > -1; --i){
var method = methods[i];
//若对象不存在该属性,或者该属性不是方法,那么抛出错误
if(typeof object[method] === 'undefined' || typeof object[method] !== 'function'){
throw new Error("Function Interface.ensureImplents: object does not implent the " +
inface.name + "interface. Method " + method + " was not found." );
}
}
};
for (var i = arguments.length - 1; i > 0; --i) {
if(arguments[i] instanceof Array){
for(var j = arguments[i].length - 1; j > -1; --j){
if(!arguments[i][j] instanceof Interface){
throw new Error('Function Interface.ensureImplents expects arguments two and above to be' +
'instances of Interface');
}
_checkMethods(arguments[i][j]); //检验接口实现
}
} else if(arguments[i] instanceof Interface){
_checkMethods(arguments[i]); //检验接口实现
} else {
throw new Error('Function Interface.ensureImplents expects arguments two and above to be' +
'instances of Interface');
}
}
};
4.Interface类的使用场合
下面的代码展示了我们的接口类的运用,顺便也测试了我们的代码是否实现了相关的功能,我们将上面实现的Interface类打包成Interface.js引入:
<script type="text/javascript" src="Interface.js"></script>
<script type="text/javascript">
//2个接口实例
var ITest = new Interface('ITest', 'test0', ['test1', 'test2'], 'test3');
var ISay = new Interface('ISay', 'say0', 'say1');
//未继承的任何对象,仅是实现了ITest接口
var implentTest = {
test0: function(){
alert('test0');
},
test1:function(){
alert('test1');
},
test2:function(){
alert('test2');
},
test3:function(){
alert('test3');
}
}
var implentSay = {
say1:function(){
alert('say1');
},
say0:function(){
alert('say0');
}
};
function TestAndSay (){
this.say0 = function(){
alert('test0 and say0');
};
this.say1 = function(){
alert('tes1t and say1');
};
}
TestAndSay.prototype = implentTest; //一个简单的继承
var implentTestAndSay = new TestAndSay(); //实例化
function test(ITestInstance){
Interface.ensureImplents(ITestInstance, ITest);
ITestInstance.test0();
ITestInstance.test1();
ITestInstance.test2();
}
function say(ISayInstance){
Interface.ensureImplents(ISayInstance, ISay);
ISayInstance.say0();
ISayInstance.say1();
}
function sayAndtest(inst){
Interface.ensureImplents(inst, ISay, ITest);
inst.test0();
inst.say0();
inst.say1();
}
test(implentTest);//弹出3个对话框:test0 test1 test2
//test(implentSay);将会抛出错误
//say(implentTest); 将会抛出错误
say(implentSay);//弹出2个对话框 say0 say1
sayAndtest(implentTestAndSay); //弹出3个对话框: test0; test0 and say0; test1 and say1
</script>
可以看到,接口的引入降低了对象间的耦合性,我们的函数不需要知道对象是何种实例,只需要对象实现了某一套方法集合,就可以调用,是不是有点面向对象的感觉了?
在许多内置接口的语言中,通常是继承接口来实现接口方法。 而javascript中,则没有必要通过继承的方式,这是javascript的灵活,同时某种程度上说要是中缺陷,因此,若为保持代码的可读性,在javascript中,可以通过在实现某个接口的对象前通过添加注释以表明该对象实现了某个接口:
/*
* Object/Class A implents Interface of ITest, ISay
*/
5. 额外的问题:
也许很多朋友会说,这种检查接口的方式增加了浏览器的负担,这个问题可以很容易解决,接口只是作为我们开发时候的约束和管理,完全可以在发布的时候去掉接口的检查。