3. 用"鸭式辨型"来实现接口。从属性检查实现中发现一个类是否支持所实现的接口无关紧要,只要接口中所有的方法出现在类中相应的地方,那足以说明已经实现接口了。就像"如果走路像鸭子,像鸭子嘎嘎的叫,不管它贴不贴标签说自己是鸭子,那我们认为它就是鸭子"。利用辅助类来判断一个类是否存在(实现)了相应接口中所有的方法,如果不存在则代表没有实现。 复制代码 代码如下: // Interfaces var Composite = new Interface("Composite", ["add", "remove", "getChild"]); var FormItem = new Interface("FormItem", ["save"]);
接口类 复制代码 代码如下: // Interface class is for checking if an instance object implements all methods of required interface var Interface = function(name, methods) { if(arguments.length != 2) { throw new Error("Interface constructor expects 2 arguments, but exactly provided for " + arguments.length + " arguments."); } this.name = name; this.methods = []; for(var i = 0;i < methods.length; i++) { if(typeof methods[i] != "string") { throw new Error("Interface constructor expects to pass a string method name."); } this.methods.push(methods[i]); } } //static class method Interface.ensureImplements = function(instance) { if(arguments.length < 2) { throw new Error("Function Interface.ensureImplements expects at least 2 arguments, but exactly passed for " + arguments.length + " arguments."); } for(var i = 1, len = arguments.length; i < len; i++) { var interface = arguments[i]; if(interface.constructor != Interface) { throw new Error("Function Interface.ensureImplements expects at least 2 arguments to be instances of Interface."); } for(var j = 0, mLen = interface.methods.length; j < mLen; j++) { var method = interface.methods[j]; if(!instance[method] || typeof instance[method] != "function") { throw new Error("Function Interface.ensureImplements: object doesn"t implements " + interface.name + ". Method " + method + " wasn"t found."); } } } }
严格的类型检查并非总是必需的,在平时的Web前端开发中很少用到以上的接口机制。但当你面对一个复杂特别是拥有很多相似模块的系统时,面向接口编程将变得非常重要。看似降低了JS的灵活性,实质上却提高了类的灵活性,降低了类之间的耦合度,因为当你传入任何一个实现了相同接口的对象都能被正确的解析。那什么时候使用接口比较合适呢?对于一个大型项目来说,肯定有许多团队成员,并且项目会被拆分为更细粒度的功能模块,为了保证进度需提前利用"占位程序"(接口)来说明模块的功能或与已开发完成的模块之间通信时,提供一个统一的接口(API)显得相当必要。随着项目的不断推进,可能需求会不断的发生变动,各模块功能也会发生相应的变动,但彼此之间通信以及提供给上层模块的API始终保持不变,确保整个架构的稳定性和持久性。下面我们通过一个具体的示例来说明接口的实际应用。假设设计一个类自动检测结果对象(TestResult类)并格式化输出一个网页视图,没有使用接口的实现方式: 复制代码 代码如下: var ResultFormatter = function(resultObject) { if(!(resultObject instanceof TestResult)) { throw new Error("ResultFormatter constructor expects a instance of TestResult."); } this.resultObject = resultObject; } ResultFormatter.prototype.render = function() { var date = this.resultObject.getDate(); var items = this.resultObject.getResults(); var container = document.createElement("div"); var header = document.createElement("h3");
header.innerHTML = "Test Result from " + date.toUTCString(); container.appendChild(header); var list = document.createElement("ul"); container.appendChild(list);
for(var i = 0, len = items.length; i++) { var item = document.createElement("li"); item.innerHTML = items[i]; list.appendChild(item); } return container; }
首先ResultFormatter类的构造函数仅仅是检查了是否为TestResult实例,却无法保证一定实现了render中的方法getDate()和getResults()。另外,随着需求的不断变动,现在有一个Weather类,包含了getDate()和getResults()方法,却因为只能检查是否为TestResult的实例而无法运行render方法,岂不是很无语呢?解决办法是移除instanceof检查并以接口代替。 复制代码 代码如下: //create the ResultSet interface var ResultSet = new Interface("ResultSet", ["getDate", "getResults"]); var ResultFormatter = function(resultObject) { // using Interface.ensureImplements to check the resultObject Interface.ensureImplements(resultObject, ResultSet); this.resultObject = resultObject; } ResultFormatter.prototype.render = function() { // keep the same as former var date = this.resultObject.getDate(); var items = this.resultObject.getResults(); var container = document.createElement("div"); var header = document.createElement("h3");
header.innerHTML = "Test Result from " + date.toUTCString(); container.appendChild(header); var list = document.createElement("ul"); container.appendChild(list);
for(var i = 0, len = items.length; i++) { var item = document.createElement("li"); item.innerHTML = items[i]; list.appendChild(item); } return container; }