面向对象的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 相关文章推荐
jquery自定义属性(类型/属性值)
May 21 Javascript
JavaScript模拟可展开、拖动与关闭的聊天窗口实例
May 12 Javascript
基于javascript制作微博发布栏效果
Apr 04 Javascript
简单的jQuery拖拽排序效果的实现(增强动态)
Feb 09 Javascript
Vue.js 2.x之组件的定义和注册图文详解
Jun 19 Javascript
vue富文本编辑器组件vue-quill-edit使用教程
Sep 21 Javascript
vue按需加载实例详解
Sep 06 Javascript
vuex根据不同的用户权限展示不同的路由列表功能
Sep 20 Javascript
vue 实现v-for循环回来的数据动态绑定id
Nov 07 Javascript
JS性能优化实现方法及优点进行
Aug 30 Javascript
JS将指定的某个字符全部转换为其他字符实例代码
Oct 13 Javascript
详解JavaScript中的this指向问题
Feb 05 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
smarty模板引擎之内建函数用法
2015/03/30 PHP
浅谈Coreseek、Sphinx-for-chinaese、Sphinx+Scws的区别
2016/12/15 PHP
PHP实现的多维数组去重操作示例
2018/07/21 PHP
JS 的应用开发初探(mootools)
2009/12/19 Javascript
基于jquery的动态创建表格的插件
2011/04/05 Javascript
jq选项卡鼠标延迟的插件实例
2013/05/13 Javascript
jquery操作select详解(取值,设置选中)
2014/02/07 Javascript
jQuery学习笔记之jQuery原型属性和方法
2014/06/09 Javascript
深入理解JavaScript系列(44):设计模式之桥接模式详解
2015/03/04 Javascript
js实现仿百度汽车频道选择汽车图片展示实例
2015/05/06 Javascript
javascript日期计算实例分析
2015/06/29 Javascript
详解JavaScript 中getElementsByName在IE中的注意事项
2017/02/21 Javascript
Vue.js实战之利用vue-router实现跳转页面
2017/04/01 Javascript
Angular 2父子组件之间共享服务通信的实现
2017/07/04 Javascript
详解Angular CLI + Electron 开发环境搭建
2017/07/20 Javascript
js input输入百分号保存数据库失败的解决方法
2018/05/26 Javascript
基于Koa(nodejs框架)对json文件进行增删改查的示例代码
2019/02/02 NodeJs
javascript设计模式 ? 解释器模式原理与用法实例分析
2020/04/17 Javascript
浅谈Vue static 静态资源路径 和 style问题
2020/11/07 Javascript
Python字符转换
2008/09/06 Python
基于python中的TCP及UDP(详解)
2017/11/06 Python
python实现截取屏幕保存文件,删除N天前截图的例子
2019/08/27 Python
python/golang 删除链表中的元素
2020/09/14 Python
No module named ‘win32gui‘ 的解决方法(踩坑之旅)
2021/02/18 Python
蔻驰美国官网:COACH美国
2016/08/18 全球购物
加拿大最大的体育用品、鞋类和服装零售商:Sport Chek
2018/11/29 全球购物
开业庆典邀请函
2014/01/08 职场文书
揠苗助长教学反思
2014/02/04 职场文书
学校搬迁方案
2014/06/15 职场文书
欠款纠纷起诉状
2015/05/19 职场文书
《珍珠鸟》教学反思
2016/02/16 职场文书
《检阅》教学反思
2016/02/22 职场文书
股东出资协议书
2016/03/21 职场文书
SpringBoot生成License的实现示例
2021/06/16 Java/Android
python多线程方法详解
2022/01/18 Python
如何利用python实现Simhash算法
2022/06/28 Python