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

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

时间: 2024-09-28 16:37:45

面向对象的Javascript之二(接口实现介绍)_js面向对象的相关文章

面向对象的Javascript之三(封装和信息隐藏)_js面向对象

同时,我们知道在面向对象的高级语言中,创建包含私有成员的对象是最基本的特性之一,提供属性和方法对私有成员进行访问来隐藏内部的细节.虽然JS也是面向对象的,但没有内部机制可以直接表明一个成员是公有还是私有的.还是那句话,依靠JS的语言灵活性,我们可以创建公共.私有和特权成员,信息隐藏是我们要实现的目标,而封装是我们实现这个目标的方法.我们还是从一个示例来说明:创建一个类来存储图书数据,并实现可以在网页中显示这些数据. 1. 最简单的是完全暴露对象.使用构造函数创建一个类,其中所有的属性和方法在外部

JavaScript OOP面向对象介绍_js面向对象

OOP 语言使我们有能力自定义对象和变量类型. 面向对象编程 JavaScript 是面向对象的编程语言 (OOP).OOP 语言使我们有能力定义自己的对象和变量类型.对象拥有属性和方法. 属性 属性指与对象有关的值. 举例: 复制代码 代码如下: <script type="text/javascript"> var txt="Hello World!" document.write(txt.length) //使用字符串对象的长度属性来计算字符串中的

JavaScript 面向对象的之私有成员和公开成员_js面向对象

上两节讲了 JavaScript 面向对象之命名空间 与 javascript 面向对象的JavaScript类,大家可以先看上面的再继续往下看. 其实很简单,废话少说,看了下面的代码及注释相信你就会一目了然! 复制代码 代码如下: //声明类,就是一个方法,其实在JavaScript中,命名空间.类.成员.... 一切皆对象 MyClass =function(){ var _this=this; //私有变量 var aa="11"; //公开变量 this.bb="22

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

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

JavaScript中的面向对象介绍_js面向对象

对象 创建 对象 构造函数 公有.私有.特权.静态成员 this.call和apply 异常处理 继承 原型 对象 在JavaScript,可以说everything is object,那么什么是对象?对象就是包含一组变量和函数的集合.在其它面向对象语言中对象是由类的实例化而来,JavaScript是基于原型的面向对象语言,没有类的概念,对象派生自现有对象的副本.JavaScript中对象可以分为两类:Function和Object. 创建对象 为了提高效率,JavaScript自带内置对象,

brook javascript框架介绍_js面向对象

brook引用了UNIX下的pipe概念,轻松把所有的处理都串联起来以共同完成任务.前一个处理的输出作为后一个处理的输入来完成参数的传递.通过brook你可以以MVC的方式来写你的javascript程序. http://hirokidaichi.github.com/brook/ brook 下载brook框架使用namespace库用于模块的组织. 这里再次用例子说明一下namespace的使用方法: 复制代码 代码如下: // 定义一个sample命名空间 Namespace('sampl

Javascript 面向对象(二)封装代码_js面向对象

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

javascript中类的定义及其方式(《javascript高级程序设计》学习笔记)_js面向对象

关于javascript中类的继承可以参考阮一峰的Blog<Javascript继承机制的设计思想>,说的很透. 一.在javascript中实例化遇到的问题: 下面用<javascript高级程序设计>中的例子来做说明,假如现在定义了一个car的对象,它是Object类的实例.像下面这样的: 复制代码 代码如下: var oCar=new Object(); oCar.color = "red"; oCar.doors = 4; oCar.mpg = 23;

javascript 面向对象全新理练之数据的封装_js面向对象

今天主要讨论如何在 JavaScript 脚本中实现数据的封装(encapsulation).数据封装说的简单点就是把不希望调用者看见的内容隐藏起来.它是面向对象程序设计的三要素之首,其它两个是继承和多态,关于它们的内容在后面再讨论. 关于数据封装的实现,在 C++.Java.C# 等语言中是通过 public.private.static 等关键字实现的.在 JavaScript 则采用了另外一种截然不同的形式.在讨论如何具体实现某种方式的数据封装前,我们先说几个简单的,大家所熟知却又容易忽略