面向对象的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 Event学习第十一章 按键的检测
Feb 10 Javascript
javascript代码运行不出来执行错误的可能情况整理
Oct 18 Javascript
JS对select控件option选项的增删改查示例代码
Oct 21 Javascript
javascript时间函数大全
Jun 30 Javascript
详谈JavaScript 匿名函数及闭包
Nov 14 Javascript
文字垂直滚动之javascript代码
Jul 29 Javascript
Bootstrap表单布局样式源代码
Jul 04 Javascript
jquery判断类型是不是number类型的实例代码
Oct 07 Javascript
Vue.js手风琴菜单组件开发实例
May 16 Javascript
vue.js动态数据绑定学习笔记
May 19 Javascript
基于Vue的延迟加载插件vue-view-lazy
May 21 Javascript
element-ui tooltip修改背景颜色和箭头颜色的实现
Dec 16 Javascript
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
Zend 输出产生XML解析错误
2009/03/03 PHP
php验证邮箱和ip地址最简单方法汇总
2015/10/30 PHP
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
关于JavaScript与HTML的交互事件
2013/04/12 Javascript
由点击页面其它地方隐藏div所想到的jQuery的delegate
2013/08/29 Javascript
NodeJS Web应用监听sock文件实例
2015/02/18 NodeJs
JS运动相关知识点小结(附弹性运动示例)
2016/01/08 Javascript
jquery自定义插件开发之window的实现过程
2016/05/06 Javascript
jQuery simpleModal插件的使用介绍
2016/08/30 Javascript
使用jQuery操作DOM的方法小结
2017/02/27 Javascript
JS正则表达式验证中文字符
2017/05/08 Javascript
vue服务端渲染页面缓存和组件缓存的实例详解
2018/09/18 Javascript
vue拖拽组件使用方法详解
2018/12/01 Javascript
微信小程序获取用户绑定手机号方法示例
2019/07/21 Javascript
vue实现购物车小案例
2019/09/27 Javascript
Vue将props值实时传递 并可修改的操作
2020/08/09 Javascript
[01:03:13]VG vs Pain 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
[01:11:21]DOTA2-DPC中国联赛 正赛 VG vs Elephant BO3 第一场 3月6日
2021/03/11 DOTA
python ElementTree 基本读操作示例
2009/04/09 Python
python解析html开发库pyquery使用方法
2014/02/07 Python
详解在Python程序中使用Cookie的教程
2015/04/30 Python
Python编程判断这天是这一年第几天的方法示例
2017/04/18 Python
python调用tcpdump抓包过滤的方法
2018/07/18 Python
Python中的asyncio代码详解
2019/06/10 Python
python3 tkinter实现点击一个按钮跳出另一个窗口的方法
2019/06/13 Python
在Python中append以及extend返回None的例子
2019/07/20 Python
python 利用zmail库发送邮件
2020/09/11 Python
SportsDirect.com新加坡:英国第一体育零售商
2019/03/30 全球购物
C#如何允许一个类被继承但是避免这个类的方法被重载?
2015/02/24 面试题
优秀体育委员自荐书
2014/01/31 职场文书
机械加工与数控专业自荐书
2014/06/04 职场文书
软件工程毕业生自荐信
2014/07/04 职场文书
个人查摆问题整改措施
2014/10/04 职场文书
经验交流材料格式
2014/12/30 职场文书
鲁迅故里导游词
2015/02/05 职场文书
2016秋季小学开学寄语
2015/12/03 职场文书