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

就足以说明接口在面向对象的领域中有多重要。但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模块化设计带来的益处。

(0)

相关推荐

  • javascript面向对象入门基础详细介绍

    什么是对象 简单点说,编程语言中的对象是对现实中事物的简化.例如,我们一个人就是一个对象,但是编程语言很难完全描述一个这样复杂的对象.所以我们必须做出简化,首先,将人简化成属性和行为的组合,然后仅仅保留对程序有意义的几个属性以及行为.例如,我们做一个统计某学校的人的身高的程序,那么我们在这个程序中就可以把人的行为省略掉,只保留行为,并且只保留身高这一个属性.这样,我们就得到了一个最简单的对象. JavaScript字符串对象 对象的属性 其实我们之前在HTML DOM中已经就是在使用对象了.例如

  • 归纳下js面向对象的几种常见写法总结

    //定义Circle类,拥有成员变量r,常量PI和计算面积的成员函数area() 1.工厂方式 var Circle = function() { var obj = new Object(); obj.PI = 3.14159; obj.area = function( r ) { return this.PI * r * r; } return obj; } var c = new Circle(); alert( c.area( 1.0 ) ); 2.比较正规的写法 function Ci

  • JS面向对象基础讲解(工厂模式、构造函数模式、原型模式、混合模式、动态原型模式)

    什么是面向对象?面向对象是一种思想!(废话). 面向对象可以把程序中的关键模块都视为对象,而模块拥有属性及方法.这样我们如果把一些属性及方法封装起来,日后使用将非常方便,也可以避免繁琐重复的工作.接下来将为大家讲解在JS中面向对象的实现.   工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,而由于在ECMAScript中无法创建类,因此用函数封装以特定接口创建对象.其实现方法非常简单,也就是在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可. function createBlo

  • js中的面向对象入门

    什么是对象 我们先来看高程三中是如何对对象进行定义的 "无序属性的集合,其属性可以包括基本值.对象或者函数",对象是一组没有特定顺序的的值.对象的没个属性或方法都有一个俄名字,每个名字都映射到一个值. 简单来理解对象就是由属性和方法来组成的 面向对象的特点 封装 对于一些功能相同或者相似的代码,我们可以放到一个函数中去,多次用到此功能时,我们只需要调用即可,无需多次重写. 在这里我们可以理解为创造对象的几种模式:单例模式,工厂模式,构造函数模式,原型模式等. 继承 子类可以继承父类的属

  • JS基于面向对象实现的拖拽功能示例

    本文实例讲述了JS基于面向对象实现的拖拽功能.分享给大家供大家参考,具体如下: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <style> #div1 {width:100px; height:100px; background:red; position:absolute;} #div2 {width:100px; height:100px; background:yellow; posit

  • Javascript 面向对象(一)(共有方法,私有方法,特权方法)

    私有方法:私有方法本身是可以访问类内部的所有属性的,即私有属性和公有属性.但是私有方法是不可以在类的外部被调用. 私有方法写法: 复制代码 代码如下: function myClass () { var private_attribute = initial_value; function private_method () { } var private_method2 = function () { } } 实例showpet()就是一个私有方法 复制代码 代码如下: var pet=fun

  • JS面向对象编程之对象使用分析

    因为大家总是用面向过程的编程思想来写JS代码,而且也由于网络上充斥了太多小"巧"的JS代码段,很多都是随意而为,很不规范,这也就造成了大家对JS的"误解",一味的认为它就是一个辅助的小东东,而不适合做大的东西开发.但是自从AJAX兴起后,大量的JS代码编写要求人们具备像写JAVA类似的代码一样,能够面向对象进行开发. 所以下面就结合我自己的体会和所学习的东东和大家一起来学习在JS中如何使用面向对象的编程.其实使用JS进行面向对象开发也不是很难的事情,因为在JS中每一

  • Javascript之面向对象--封装

    第一步:做一个"手机的类" var MobilePhone = (function(){ ---- })() 第二步:考虑这个类,里需要那些类的私有属性,这里我想定义的是实例出来手机的数量 var MobilePhone = (function(){ //私有属性 var count = 0; //代表手机的数量 })() 第三步:创建一个构造函数,即实例时候,对产生的新象的一个初始化,例如属性,方法的初始化;在这个例子中,每一个手机都会有颜色,大小,价格属性.这里的构造函数也是一个闭

  • javascript中的面向对象

    相信大家对javascript中的面向对象写法都不陌生,那还记得有几种创建对象的写法吗?相信大家除了自己常写的都有点模糊了,那接下来就由我来帮大家回忆回忆吧! 1. 构造函数模式 通过创建自定义的构造函数,来定义自定义对象类型的属性和方法. function cons(name,age){ this.name = name; this.age = age; this.getMes = function(){ console.log(`my name is ${this.name},this ye

  • JS 面向对象之神奇的prototype

    JavaScript中对象的prototype属性,可以返回对象类型原型的引用.这是一个相当拗口的解释,要理解它,先要正确理解对象类型(Type)以及原型(prototype)的概念. 1 什么是prototype JavaScript中对象的prototype属性,可以返回对象类型原型的引用.这是一个相当拗口的解释,要理解它,先要正确理解对象类型(Type)以及原型(prototype)的概念. 前面我们说,对象的类(Class)和对象实例(Instance)之间是一种"创建"关系,

  • JavaScript面向对象编写购物车功能

    之前的项目中需要一个购买数据商品并付款的功能,刚开始一直不敢使用面向对象的写法,主要是没有理清思路,而且那时的数据商品比较的复杂,就一直没敢动,在网上也找些面向对象的写法,把思路理清一遍,就想自己试着写写. 接下来我会一步一步分析,面向对象的写法过程.整个流程大致分为:                1.先定义好一个商品列表的数据形式和商品总数集,类似: var data = [{name: 'name', unitPrice: 10, num: 2}]; var total = {type:

随机推荐