面向对象的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面向对象之静态与非静态类
Feb 03 Javascript
杨氏矩阵查找的JS代码
Mar 21 Javascript
jQuery中after的两种用法实例
Jul 03 Javascript
JS、DOM和JQuery之间的关系示例分析
Apr 09 Javascript
Jquery ui datepicker设置日期范围,如只能隔3天【实现代码】
May 04 Javascript
Vue实现双向绑定的方法
Dec 22 Javascript
layui分页效果实现代码
May 19 Javascript
实现一个完整的Node.js RESTful API的示例
Sep 29 Javascript
redux-saga 初识和使用
Mar 10 Javascript
JS使用数组实现的队列功能示例
Mar 04 Javascript
jQuery zTree插件使用简单教程
Aug 16 jQuery
nuxt框架中对vuex进行模块化设置的实现方法
Sep 06 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
简单实用的网站PHP缓存类实例
2014/07/18 PHP
使用PHPMailer实现邮件发送代码分享
2014/10/23 PHP
PHP面向对象程序设计子类扩展父类(子类重新载入父类)操作详解
2019/06/14 PHP
Laravel 读取 config 下的数据方法
2019/10/13 PHP
MooTools 页面滚动浮动层智能定位实现代码
2011/08/23 Javascript
原生js拖拽(第一课 未兼容)拖拽思路
2013/03/29 Javascript
jQuery查询数据返回object和字符串影响原因是什么
2013/08/09 Javascript
页面加载完后自动执行一个方法的js代码
2014/09/06 Javascript
使表格的标题列可左右拉伸jquery插件封装
2014/11/24 Javascript
JS实现的新浪微博大厅文字内容滚动效果代码
2015/11/05 Javascript
获取IE浏览器Cookie信息的方法
2017/01/23 Javascript
浅析vue component 组件使用
2017/03/06 Javascript
Angular实现响应式表单
2017/08/04 Javascript
angularjs实现的购物金额计算工具示例
2018/05/08 Javascript
JS 实现微信扫一扫功能
2018/09/14 Javascript
Jquery实现无缝向上循环滚动列表的特效
2019/02/13 jQuery
微信小程序保存多张图片的实现方法
2019/03/05 Javascript
JavaScript中十种一步拷贝数组的方法实例详解
2019/04/22 Javascript
详解小程序之简单登录注册表单验证
2019/05/13 Javascript
[01:05:29]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster BO3 第二场 1月24日
2021/03/11 DOTA
在Python的web框架中配置app的教程
2015/04/30 Python
Python模块搜索概念介绍及模块安装方法介绍
2015/06/03 Python
Python实现的字典值比较功能示例
2018/01/08 Python
基于Python pip用国内镜像下载的方法
2018/06/12 Python
使用OpCode绕过Python沙箱的方法详解
2019/09/03 Python
关于Pytorch的MLP模块实现方式
2020/01/07 Python
django Model层常用验证器及自定义验证器详解
2020/07/15 Python
StubHub巴西:购买和出售您的门票
2016/07/22 全球购物
求职信内容怎么写
2014/05/26 职场文书
火灾现场处置方案
2014/05/28 职场文书
医院党员公开承诺书
2014/08/30 职场文书
社区学习党的群众路线教育实践活动心得体会
2014/11/03 职场文书
学校运动会通讯稿
2015/07/18 职场文书
《学会看病》教学反思
2016/02/17 职场文书
千万级用户系统SQL调优实战分享
2022/03/03 MySQL
SQL语句中EXISTS的详细用法大全
2022/06/25 MySQL