写在前面:
在有许多程序员参与的大型项目中,接口起着至关重要的作用。程序员常常需要使用还未编写的出来的API。或者需要提供一些占位代码以免延误开发进度。接口在这种场合中的重要性表现在许多方面。它们记载着API,可作为程序员正式交流的工具。在占位代码被替换为最终的API时,你立刻就能知道所需方法是否得到了实现。在开发过程中,如果API发生了变化,只要新的API实现了同样的接口,它就能完美替换原有API。
目录:
- 用注释描述接口
- 用属性检查模仿接口
- 用鸭式辨型模仿接口
- 通用的接口实现方法
在JS中使用接口
用注释描述接口
/* * interface Composite { * function add(child); * function remove(child); * function getChild(child); * } * * interface FormItem { * function save(); * } * */ var CompositeForm = function() { //implements Composite and FormItem //... }; CompositeForm.prototype = { //Implement the interface Composite add: function(child) { //... }, remove: function(child) { //... }, getChild: function(child) { }, //Implement this interface FormItem save: function() { } }
用属性检查模仿接口
所有类都明确地声明自己都实现了哪些接口。
那些想与这些类打交道的对象可以针对这些声明进行检查
/* * interface Composite { * function add(child); * function remove(child); * function getChild(child); * } * * interface FormItem { * function save(); * } * */ var CompositeForm = function(id, method) { //implements Composite and FormItem //显式声明类实现哪些接口 this.implementsInterfaces = ['Composite', 'FormItem']; //... }; //... /* * 先判断传递的对象是否实现了需要的接口 * 检查通过后,才能调用需要的方法 * @param {CompositeForm} formInstance */ function addForm(formInstance) { if(!implements(formInstance, 'Composite', 'FormItem')) { throw new Error("对象没有实现需要的接口"); } //... } //检查对象是否实现了提供的接口 //但并不能保证类实现了接口的所有方法 function implements(object) { //第2个参数开始是需要的不确定数量的接口 for(var i=1; i<arguments.length; i++) { var interfaceName = arguments[i]; //接口 var found = false; //标识是否实现了接口 var interfaceNames = object.implementsInterfaces; for(var j=0; j<interfaceNames.length; j++) { if(interfaceNames[j] == interfaceName) { //在接口声明的数组中找到了 found = true; break; } } if(!found) { return false; //没有实现需要的接口 } } return true; }
这种方法优点:它对类所实现的接口提供了文档说明。如果需要的接口不在一个类宣称支持的接口之列时抛出一个错误。
缺点:它并不能确保类真正实现了自称实现的接口。
用鸭式辨型模仿接口
它把对象实现的方法集作为判断它是不是某个类的实例的唯一标准。这种技术可以用来判断一个类是否实现了某个接口。背后的思想:如果对象有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。
//interfaces var Composite = Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = Interface('FormItem', ['save']); //CompositeForm class var CompositeForm = function(id, method) { //... }; function addForm(formInstance) { //检查接口的方法formInstance对象是否都实现了 ensureImplements(formInstance, 'Composite', 'FormItem'); //... }
类没有声明自己实现了哪些接口,这降低了代码的可重用性。
通用接口实现方法
var Composite = Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = Interface('FormItem', ['save']); //CompositeForm class var CompositeForm = function(id, method) { //implements Composite and FormItem //... }; function addForm(formInstance) { //检查接口的方法formInstance对象是否都实现了 Interface.ensureImplements(formInstance, 'Composite', 'FormItem'); //... }
上面使用接口(Interface)类实现
/** * 定义接口的辅助函数 * @param {String} name 接口名 * @param {Array} methods 所有方法组成的数组 */ var Interface = function(name, methods) { if(arguments.length != 2) { throw new Error('需要传递2个参数:接口名,方法名数组。'); } this.name = name; this.methods = []; for(var i=0, len=methods.length; i<len; i++) { if(typeof methods[i] != 'string' ) { throw new Error('方法名应该是字符串类型'); } this.methods.push(methods[i]); } }; Interface.ensureImplements = function(object) { //判断参数的个数不少于2个 if(arguments.length<2) { throw new Error('至少需要2个参数'); } //检查对象的方法包含支持的接口的所有方法 for(var i=1; i<arguments.length; i++) { var interface = arguments[i]; //待验证的接口 //判断接口的类型 if(interface.constructor !== Interface) { throw new Error('不是特定的接口'); } var methods = interfaceName.methods; //接口定义的所有方法 for(var j=0, len=methods.length; j<len; j++) { var method = methods[j]; if(!object[method] || typeof object[method] != 'function') { throw new Error('没有实现接口'+interface.name+'中的方法'+method); } } } }
一个理想的软件系统应该为所有类定义接口。
接口提供了一种用以说明一个对象应该具有哪些方法的手段。尽管它可以表明(暗示)这些方法的作用,但它并不规定这些方法应该如何实现。有了接口,可以按对象提供的特性对它们进行分组。
JS编写接口使用中的最大问题在于,无法强迫其他程序员遵守你定义的接口。
使用接口的难点在于判断是否必要使用它,因为它并不总是不可或缺的,还降低效率