面向对象的Javascript之二(接口实现介绍)


Posted in Javascript onJanuary 27, 2012

就足以说明接口在面向对象的领域中有多重要。但JS却不像其他面向对象的高级语言(C#,Java,C++等)拥有内建的接口机制,以确定一组对象和另一组对象包含相似的的特性。所幸的是JS拥有强大的灵活性(我在上文已谈过),这使得模仿接口特性又变得非常简单。那么到底是接口呢?

接口,为一些具有相似行为的类之间(可能为同一种类型,也可能为不同类型)提供统一的方法定义,使这些类之间能够很好的实现通信。

那使用接口到底有哪些好处呢?简单地说,可提高系统相似模块的重用性,使得不同类的通信更加稳固。一旦实现接口,则必须实现接口中所有的方法。对于大型的Web项目来说,使得多个复杂模块的相似功能模块,只需要提供接口便可以提供一个实现,而彼此之间不受到影响。但我们必须明确,接口也不是万能的,由于JS是弱类型语言,你并不能强制其他的团队成员严格遵循你所提供的接口,不过你可以使用代码规范和辅助类来缓解这个问题。另外,对系统性能也会造成一定的影响,应根据你的系统需求复杂度而定。由于没有内建的interface和implements关键字,下面我们来看看JS是如何模仿实现接口的。

1. 最简单也是效果最差实现接口的方式是使用注释。即在注释中使用interface来说明接口的意图。

/* 
interface Composite { 
function add(child); 
function remove(child); 
function getChild(index); 
} 
interface FormItem { 
funtion save(); 
} 
*/ 
var CompositeForm = function(id, name, action) { 
// implements Composite, FormItem 
} 
CompositeForm.prototype = { 
// implements Composite interface 
add: function(child) { 
//... 
}, 
remove: function(child) { 
//... 
}, 
getChild: function(index) { 
//... 
} 
// implements FormItem interface 
save: function() { 
//... 
} 
}

这并没有很好的模拟接口的功能和确保Composite类确实实现了方法的集合,也没有抛出错误通知程序员问题所在,除了说明以外不起任何作用,所有的一致性都需要程序员自觉完成。但它容易实现,不需要额外的类或函数,不影响文档的大小和执行速度,注释也能很轻易的剥离,在一定程度上提高了重用性,因为提供了类的说明可以跟其他实现相同接口的类进行通信。

2. 用属性检查模拟接口。类显示声明了要实现的接口,通过属性检查是否实现了相应的接口。

/* 
interface Composite { 
function add(child); 
function remove(child); 
function getChild(index); 
} 
interface FormItem { 
funtion save(); 
} 
*/ 
var CompositeForm = function(id, name, action) { 
this.implementsInterfaces = ["Composite", "FormItem"]; 
//... 
} 
function checkInterfaces(formInstance) { 
if(!implements(formInstance, "Composite", "FormItem")) { 
throw new Error("Object doesn't implement required interface."); 
} 
//... 
} 
//check to see if an instance object declares that it implements the required interface 
function implements(instance) { 
for(var i = 1; i < arguments.length; i++) { 
var interfaceName = arguments[i]; 
var isFound = false; 
for(var j = 0; j < instance.implementsInterfaces.length; j++) { 
if(interfaceName == instance.implementsInterfaces[j]) { 
isFound = true; 
break; 
} 
} 
if(!isFound) return false;// An interface was not found. 
} 
return true;// All interfaces were found. 
}

在这里发现,仍然添加了注释来说明接口。不过在Composite类中添加了一个属性implementsInterfaces,说明该类必须实现那些接口。通过检查该属性来判断是否实现相应的接口,如果未实现就会抛出错误。但缺点在于你仍无法判断是否真正实现了对应的接口方法,仅仅只是"自称"实现了接口,同时也增加了相应的工作量。

3. 用"鸭式辨型"来实现接口。从属性检查实现中发现一个类是否支持所实现的接口无关紧要,只要接口中所有的方法出现在类中相应的地方,那足以说明已经实现接口了。就像"如果走路像鸭子,像鸭子嘎嘎的叫,不管它贴不贴标签说自己是鸭子,那我们认为它就是鸭子"。利用辅助类来判断一个类是否存在(实现)了相应接口中所有的方法,如果不存在则代表没有实现。

// Interfaces 
var Composite = new Interface("Composite", ["add", "remove", "getChild"]); 
var FormItem = new Interface("FormItem", ["save"]); var CompositeForm = function(id, name, action) { 
// implements Composite, FormItem interfaces 
} 
function checkInterfaces(formInstance) { 
Interface.ensureImplements(formInstance, "Composite", "FormItem"); 
//... 
}

接口类
// 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; 
}

可以看出render方法没有发生任何改变。改变的仅仅是添加一个接口和使用接口来进行类型检查。同时现在能够传递Weather类的实例来进行调用,当然也能传递实现了ResultSet接口的任何类的实例,使检查更加精确和宽容。随着后续对JS设计模式的推出,接口会在工厂模式、组合模式、装饰模式和命令模式中得到广泛的应用。希望大家可以细细品味接口给我们的JS模块化设计带来的益处。
Javascript 相关文章推荐
JavaScript 学习点滴记录
Apr 24 Javascript
jQuery前台数据获取实现代码
Mar 16 Javascript
一个关于jqGrid使用的小例子(行按钮)
Nov 04 Javascript
扩展jQuery对象时如何扩展成员变量具体怎么实现
Apr 25 Javascript
JS函数this的用法实例分析
Feb 05 Javascript
JavaScript中的splice()方法使用详解
Jun 09 Javascript
js实现头像图片切割缩放及无刷新上传图片的方法
Jul 17 Javascript
javascript设置和获取cookie的方法实例详解
Jan 05 Javascript
Angular实现的敏感文字自动过滤与提示功能示例
Dec 29 Javascript
详解 vue better-scroll滚动插件排坑
Feb 08 Javascript
浅谈Angular HttpClient简单入门
May 04 Javascript
利用jquery和BootStrap实现动态滚动条效果
Dec 03 jQuery
js String对象中常用方法小结(字符串操作)
Jan 27 #Javascript
getElementByIdx_x js自定义getElementById函数
Jan 24 #Javascript
基于JQUERY的多级联动代码
Jan 24 #Javascript
JavaScript常用对象的方法和属性小结
Jan 24 #Javascript
DOM2非标准但却支持很好的几个属性小结
Jan 21 #Javascript
用js来定义浏览器中一个左右浮动元素相对于页面主体宽度的位置的函数
Jan 21 #Javascript
兼容ie、firefox的图片自动缩放的css跟js代码分享
Jan 21 #Javascript
You might like
高性能PHP框架Symfony2经典入门教程
2014/07/08 PHP
Thinkphp中数据按分类嵌套循环实现方法
2014/10/30 PHP
Laravel 5框架学习之子视图和表单复用
2015/04/09 PHP
php检测mysql表是否存在的方法小结
2017/07/20 PHP
php实现微信发红包功能
2018/07/13 PHP
js TextArea的选中区域处理
2010/12/28 Javascript
阻止子元素继承父元素事件具体思路及实现
2013/05/02 Javascript
JavaScript实现点击按钮后变灰避免多次重复提交
2013/07/15 Javascript
javascript函数式编程程序员的工具集
2015/10/11 Javascript
js+canvas简单绘制圆圈的方法
2016/01/28 Javascript
原生JavaScript制作微博发布面板效果
2016/03/11 Javascript
JS实用技巧小结(屏蔽错误、div滚动条设置、背景图片位置等)
2016/06/16 Javascript
angular.js指令中的controller、compile与link函数的不同之处
2017/05/10 Javascript
vue-cli3.0 特性解读
2018/04/22 Javascript
Node.js API详解之 dns模块用法实例分析
2020/05/15 Javascript
antd-DatePicker组件获取时间值,及相关设置方式
2020/10/27 Javascript
Webpack的Loader和Plugin的区别
2020/11/09 Javascript
用vite搭建vue3应用的实现方法
2021/02/22 Vue.js
python实现集中式的病毒扫描功能详解
2019/07/09 Python
django 实现将本地图片存入数据库,并能显示在web上的示例
2019/08/07 Python
python 画函数曲线示例
2019/12/04 Python
python函数不定长参数使用方法解析
2019/12/14 Python
python tkinter实现连连看游戏
2020/11/16 Python
最新Python idle下载、安装与使用教程图文详解
2020/11/28 Python
美国知名艺术画网站:Art.com
2017/02/09 全球购物
swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上
2013/07/06 面试题
python re模块和正则表达式
2021/03/24 Python
销售代表求职自荐信
2013/10/01 职场文书
毕业生就业自荐信
2013/12/04 职场文书
实用求职信范文分享
2013/12/25 职场文书
学校岗位设置方案
2014/01/16 职场文书
初中科学教学反思
2014/01/21 职场文书
水污染治理工程专业求职信
2014/06/14 职场文书
2014年药店店长工作总结
2014/11/17 职场文书
拾金不昧表扬信
2015/01/16 职场文书
Golang二维数组的使用方式
2021/05/28 Golang