JavaScript设计模式 Item 2 -- 接口的实现

1、接口概述

1。什么是接口?

接口是提供了一种用以说明一个对象应该具有哪些方法的手段。尽管它可以表明这些方法的语义,但它并不规定这些方法应该如何实现。

2. 接口之利

  • 促进代码的重用。
    接口可以告诉程序员一个类实现了哪些方法,从而帮助其使用这个类。

  • 有助于稳定不同类之前的通信方式。

  • 测试和调式因此也能变得更轻松。
    在javascript这种弱类型语言中,类型不匹配错误很难跟踪。使用接口可以让这种错误的查找变午更容易一点,因为此时如果一个对象不像所要求的类型,或者没有实现必要的方法,那么你会得到包含有用信息的明确的错误提示。这样一来,逻辑错误可以被限制在方法自身,而不是在对象构成之中。

  • 接口还能让代码变得更稳固.
    因为对接口的任何改变在所有实现它的类都必须体现出来。如果接口添加了一个操作,而某个实现它的类并没有相应的添加这个操作,那么你肯定会立即见到一个错误。

3. 接口之弊

javascript是一种具有极强表现图片的语言,这主要得益于其弱类型的特点。而接口的使用则一定程序上强化了类型的作用。这降低了语言的灵活性。javascript并没有提供对接口的内置支持,而试图模仿其它语言内置的功能总会有一些风险。

js中接口使用的最大问题在于,无法强迫其他程序员遵守你定义的接口。在其它语言中,接口的概念是内置的,如果某人定义了实现一个接口的类,那么编译器会确保该类的确实现了这个接口。而在javascript中则必须用手工的办法保证某个类实现了一个接口。编码规范和辅助类可以提供一些帮助,但无法彻底根除这个问题。如果项目的其他程序员不认真对待接口,那么这些接口的使用是无法得到强制性保证的。除非项目的所有人都同意使用接口并对其进行检查,否则接口的很多价值都无从体现。

2、在javascript中模仿接口

javascript中模仿接口的三种方法:注解描述法、属性检查法、鸭式辨型法。

没有哪种技术是完美的,但三者结合使用基本上可以令人满意。

1、注释描述法实现接口

用注释模仿接口是最简单的方法,但效果却是最差的。这种方法模仿其他页面对象语言中的做法,使用了interface和implements关键字,但把它们放在注释中,以免引起语法错误。如下:


//javascript中定义接口的方式有三种:
//1、注解描述的方式/**   * interface Composite{
* function add(obj);
* function remove(obj);
* function update(obj);
}优点:程序员可以有参考
缺点:缺点一大堆,他只是一个借口的文档范畴,假如不实现所有的方法,程序照样可以运行,太松散了。对测试和调试难度大
*/// Implement of interface Composite
var CompositeImpl =function(){/*this.add = function(obj){};this.remove = function(obj){};这种函数定义的方法,在实例化一个对象的时候,new一个示例,将产生一个方法,且各个实力的方法还不一样。所以采用下面的方法:*/CompositeImpl.prototype.add = function(obj){}CompositeImpl.prototype.remove = function(obj){}       CompositeImpl.prototype.update = function(obj){}
}var c1 = new CompositeImpl();
var c2 = new CompositeImpl()alert(c1.add == c2.add)

这种模仿并不是很好。它没有为确保Composite真正实现了正确的方法集而进行检查,也不会抛出错误以告知程序员程序中的问题。说到底它主要还是属于程序文档范畴。在这种做法中,对接口约定的遵守完全依靠自觉。

2、属性检测法实现接口

这种方法更严谨一点。所有类都明确地声明自己实现了哪些接口,那些想与这些类打交道的对象可能针对这些声明进行检查。那些接口自身仍然只是注释,但现在你可以通过检查一个属性得知某个类自称实现了什么接口。

/** * interface Composite{*     function add(obj);*     function remove(obj);*     function update(obj);*  }* interface FormItem{*     function select(obj);* }*/// CompositeImpl implements interface Composite,FormItemvar CompositeImpl =function(){//显示在类的内部,接收所实现的接口,一般来说,这是一个规范,// 我们项目经理:在内部类定义一个数组,名字要固定this.interfaceImplments = ['Composite','FormItem'];CompositeImpl.prototype.add = function(obj){alert("小平果");}CompositeImpl.prototype.remove = function(obj){}       CompositeImpl.prototype.update = function(obj){}/*CompositeImpl.prototype.select = function(obj){}*/}//定义函数检测,判断当前对象是否实现了所有的接口function checkCompositeImpl (instance){if (!isImplments(instance,'Composite','FormItem')) {throw new Error('Object cannot implements all the interface');};}//公用的具体检测方法(核心方法),主要目的就是判断示例对象有没有实现相关的接口;function isImplments(object){//arguments 对象会的函数的实际对象for (var i = 1, len = arguments.length; i < len; i++) { //注意这里从1开始,逐个方法判断。var interfaceName = arguments[i];           //接收实现每一个接口的名字var interfaceFound = false;//判断此方法到底是实现了还是失败了?规范里定义了interfaceImplments.for (var j = 0;j < object.interfaceImplments.length; j++) {if(object.interfaceImplments[j] == interfaceName){interfaceFound =  true;break;}};//如果没有实现,则返回falseif (!interfaceFound) {return false;};}return true;}var c1 = new CompositeImpl();
checkCompositeImpl(c1);c1.add();

这个例子中,CompositeImpl 宣称自己实现了Composite接口,其做法是把这两个接口名称加入一个名为implementsInterfaces的数组。类显式声明自己支持什么接口。任何一个要求基于参数属于特定类型的函数都可以对这个属性进行检查,并在所需接口未在声明之列时抛出一个错误。

这种方法有几个优点。它对类所实现的接口提供了文档说明。如果需要的接口不在一个类宣称支持的接口之列,你会看到错误消息。通过利用这些错误,你可以强迫其他程序员声明这些接口。

这种方法的主要缺点在于它并未确保类真正实现了自称实现的接口。你只知道它是否说自己实现了接口。在创建一个类时声明它实现了一个接口,但后来在实现该接口所规定的方法时却漏掉其中的某一个,这种错误很常见。此时所有检查都能通过,但那个方法却不存在,这将在代码中埋下一个隐患。另外显式声明类所支持的接口也需要一些额外的工作。

3、鸭式辨型法实现接口

其实,类是否声明自己支持哪些接口并不重要,只要它具有这些接口中的方法就行。鸭式辨型(这个名称来自James Whitomb Riley的名言:“像鸭子一样走路并且嘎嘎叫的就是鸭子”)正是基于这样的认识。它把对象实现的方法集作作为判断它是不是某个类的实例的唯一标准。这种技术在检查一个类是否实现了某个接口时也可大显向身手。这种方法背后的观点很简单:如果对象具有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。你可以用一个辅助函数来确保对象具有所有必需的方法:

/*  实现接口的第三种方式:鸭式辨型发实现接口,(较为完美的实现方法)核心思想:一个类实现接口的主要目的:把其中的方法都实现了(检测方法)完全面向对象  代码实现统一,实现解耦*///1、接口类---Class Interface ===>实例化N多个接口/***接口类的参数?几个* 参数1:接口名* 参数2:接收方法的集合(数组)*/
var Interface = function(name , methods){//判断接口的参数个数if (arguments.length !=2) {throw new Error('the instance interface constructor arguments should be 2');};this.name =name;//this.methods = methods;this.methods = [];for (var i = 0, len = methods.length; i <len; i++) {if (typeof methods[i] !== "string"){throw new Error('the name of method is wrong');}this.methods.push(methods[i]);} 
}//2、准备工作,具体的实现//(1)实例化接口对象
var CompositeInterface = new Interface('CompositeInterface',['add','delete']);
var FormItemInterface = new Interface('FormItemInterface',['update','select']);//(2)具体的实现类
//CompositeImpl implments  CompositionIterface FormItemIterface
var CompositeImpl = function(){}//(3)实现接口的方法 implements methods
CompositeImpl.prototype.add = function(obj){alert("add");
}
CompositeImpl.prototype.delete = function(obj){alert("delete");
}         
CompositeImpl.prototype.update = function(obj){alert("update");
}
/*CompositeImpl.prototype.select = function(obj){alert("select");
}*///3、检验接口里的方法
//如果检测通过,不做任何操作;不通过,则抛出异常。
//这个方法的目的就是 检测方法的Interface.ensureImplements =function(object){//如果接受参数长度小于2 ,证明还有任何实现的接口if (arguments.length < 2) {throw new Error('The Interface has no implement class');};//获得接口的实例对象for (var i = 1,  len= arguments.length; i < len; i++) {var instanceInterface =arguments[i];//判断参数是否为 接口类的类型if (instanceInterface.constructor !==Interface) {throw new Error('The arguments constructor is not Interface Class');};for (var j = 0, len2 =instanceInterface.methods.length ; j <len2; j++ ) {//用一个临时变量 ,接收每个方法的名字(注意为字符串类型)var methodName =  instanceInterface.methods[j];//object[key] 获得方法if (!object[methodName] || typeof object[methodName] !== 'function'){throw new Error('the method"'+ methodName+'"is not found');}}}
}var c1 =new CompositeImpl();
Interface.ensureImplements(c1,CompositeInterface,FormItemInterface);
c1.add();

与另外两种方法不同,这种方法并不借助注释。其各个方面都是可以强制实施的。ensureImplements函数需要至少两个参数。第一个参数是想要检查的对象。其余参数是据以对那个对象进行检查的接口。该函数检查其第一个参数代表的对象是否实现了那些接口所声明的所有方法。如果发现漏掉了任何一个方法,它就会抛出错误,其中包含了所缺少的那个方法和未被正确实现的接口的名称等有用信息。这种检查可以用在代码中任何需要确保某个对象实现了某个接口的地方。在本例中,addForm函数仅当一个表单对象支持所有必要的方法时才会对其执行添加操作。

尽管鸭式辨型可能是上述三种方法中最有用的一种,但它也有一些缺点。这种方法中,类并不声明自己实现了哪些接口,这降低了代码的可重用性,并且也缺乏其他两种方法那样的自我描述性。它需要使用一个辅助类Interface和一个辅助函数ensureImplements。而且,它只关心方法的名称,并不检查其参数的名称、数目或类型。

3、Interface类的使用场合

严格的类型检查并不总是明智的。许多js程序员根本不用接口或它所提供的那种检查,也照样一干多年。接口在运用设计模式实现复杂系统的时候最能体现其价值。它看似降低javascript的灵活性,而实际上,因为使用接口可以降低对象间的耦合程度,所以它提高了代码的灵活性。接口可以让函数变得更灵活,因为你既能向函数传递任何类型的参数,又能保证它只会使用那些具有必要方法的对象。

4、Interface类的用法

判断代码中使用接口是否划算是最重要的一步。对于小型的、不太费事的项目来说,接口的好处也许并不明显,只是徒增其复杂度而已。你需要自行权衡其利弊。如果认为在项目中使用接口利大于弊,那么可以参照如下使用说明:
1、 将Interface类纳入HTML文件。
2、 逐一检查代码中所有以对象为参数的方法。搞清代码正常运转要求的这些对象参数具有哪些方法
3、 为你需要的每一个不同的方法集创建一个Interface对象。
4、 剔除所有针对构造器显式检查。因为我们使用是鸭式辨型,所以对象的类型不再重要。
5、 以Interface.ensureImplements取代原来的构造器检查。

示例
假设你要创建一个类,它可以将一些自动化测试结果转化为适于在网页上查看的格式。该类的构造器以一个TestResult类的实例为参数。它会应客户的请求对这个TestResult对象所封装的数据进行格式化,然后输出。
原始定义:

 var ResultFormatter =function(resultsObject){if(!(resultsObject instanceof TestResult)){throw newError("ResultsFormatter:constructor requires an instance of TestResult asan argument.")}this.resultsObject = resultsObject;}ResultFormatter.prototype.renderResults =function(){var dateOfTest = this.resultsObject.getDate();var resultsArray =this.resultsObject.getResults();var resultsContainer =document.createElement('div');var resultsHeader =document.createElement("h3");resultsHeader.innerHTML = "TestResults from "+dateOfTest.toUTCString();resultsContainer.appendChild(resultsHeader);var resultList =document.createElement("ul");resultsContainer.appendChild(resultList);for(var i=0,len=resultsArray.length;i<len;i++){var listItem=document.createElement('li');listItem.innerHTML =resultsArray[i];resultList.appendChild(listItem);}return resultsContainer;}

该类的构造器会对参数进行检查,以确保其的确为TestResult类的实例。如果参数达不到要示,构造器将抛出一个错误。有了这样的保证,在编写renderResults方法时,你就可以认定有getDate和getResults这两个方法可供使用。实际上这并不能保证所需要的方法得到了实现。TestResult类可能会被修改,致使其不再拥有getDate()方法。在此情况下,构造器中的检查仍能通过,但renderResults方法却会失灵。

此外,构造器的这个检查施加了一些不必要的限制。它不允许使用其他类的实例作为参数,哪怕它们原本可以如愿发挥作用。例如,有一个名为WeatherData在也拥有getDate和getResults这两个方法。它本来可以被ResultFormatter类用得好好的。但是那个显式类型检查会阻止使用WeatherData类的任何实例。
问题解决办法是删除那个使用instanceOf的检查,并用接口代替它。首先,我们需要创建这个接口:

//ResultSetInterface.
var ResultSet =new Interface(“ResultSet”,[‘getDate’,’getResults’]);

上面的这行代码创建了一个Interface对象的新实例。第一个参数是接口的名称,第二个参数是一个字符串数组,其中的每个字符串都是一个必需的方法名称。有了这个接口之后,就可以用接口检查替代instanceOf检查了

var ResultFormatter = function(resultsObject){Interface.ensureImplements(resultsObject,ResultSet);this.resultsObject = resultsObject;
}
ResultFormatter.prototype.renderResults= function(){…
}

renderResults方法保持不变。而构造器则被改为使用ensureImplements方法而不是instanceof运算符。现在构造器可以接受WeatherData或其他任何实现所需要方法的类的实例。我们只修改了几行ResultFormatter类代码,就让那个检查变得更准确,而且更宽容。

5、依赖于接口的设计模式

  • 工厂模式
  • 组合模式
  • 装饰模式
  • 命令模式

版权声明:本文为小平果原创文章,转载请注明:http://blog.csdn.net/i10630226

转载于:https://www.cnblogs.com/dingxiaoyue/p/4948175.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458713.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 乐观锁加锁失败 - 集成AOP

Spring Boot with AOP 手头上的项目使用了Spring Boot&#xff0c; 在高并发的情况下&#xff0c;经常出现乐观锁加锁失败的情况&#xff08;OptimisticLockingFailureException&#xff0c;同一时间有多个线程在更新同一条数据&#xff09;。为了减少直接向服务使用者直接返回…

掌握VS2010调试 -- 入门指南

1 导言 在软件开发周期中&#xff0c;测试和修正缺陷&#xff08;defect&#xff0c;defect与bug的区别&#xff1a;Bug是缺陷的一种表现形式&#xff0c;而一个缺陷是可以引起多种Bug的&#xff09;的时间远多于写代码的时间。通常&#xff0c;debug是指发现缺陷并改正的过程。…

151031

create or replace procedure pr_test1 is v_case number(3): 100; beginif 2>1 thendbms_output.put_line(成立);elsif 4>3 thenif 7>6 thendbms_output.put_line(不成立);end if; elsif 6>5 thendbms_output.put_line(也行);elsedbms_output.put_line(也不成立);…

postgresql9.5 run 文件linux安装后配置成开机服务

网上出现的比较多安装方法要么是源码安装&#xff0c;要么是yum安装&#xff0c;我发觉都要配置很多属性&#xff0c;比较麻烦&#xff0c;所以现在我在centos7长用 run文件来安装 http://get.enterprisedb.com/postgresql/postgresql-9.5.1-1-linux-x64.run 这里的安装shell整…

Windows API GetProcAddress 及demo code

GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。 函数原型&#xff1a; FARPROC GetProcAddress( HMODULE hModule, // DLL模块句柄 LPCSTR lpProcName// 函数名 ); 参数&#xff1a; hModule [in] 包含此函数的DLL模块的句柄。LoadLibrary、AfxLoadLibrary …

【操作系统】进程管理

进程管理 进程的基本概念 程序的顺序执行及其特征 程序的顺序执行:仅当前一操作(程序段)执行完后&#xff0c;才能执行后续操作。 程序顺序执行时的特征&#xff1a;顺序性&#xff0c;封闭性&#xff0c;可再见性。 前趋图 前趋图(Precedence Graph)是一个有向无循环图&#…

va_list va_start va_end的使用

<pre name"code" class"cpp" style"color: rgb(51, 51, 51); white-space: pre-wrap; word-wrap: break-word;"><strong>一、 从printf()开始</strong> 从大家都很熟悉的格式化字符串函数开始介绍可变参数函数。 原型&#xf…

Linux学习之CentOS(三)----将Cent0S 7的网卡名称eno16777736改为eth0

【正文】 Linux系统版本&#xff1a;CentOS_7&#xff08;64位&#xff09; 一、前言&#xff1a; 今天又从Centos 6.5装回了Centos 7&#xff0c;毕竟还是要顺应潮流嘛。安装完成之后&#xff0c;发现发现CentOS 7默认的网卡名称是eno16777736&#xff0c;如图所示&#xff1a…

本地音频播放,使用AVFoundation.framework中的AVAudioPlayer来实现

本地音频播放,使用AVfoundation.framework中的AVAudioPlayer来实现 /*AVAudioPlayer的使用比较简单: 1、初始化AVAudioPlayer对象&#xff0c;此时通常指定本地文件路径 2、设置播放器属性&#xff0c;例如重复次数、音量大小等 3、调用play方法播放。 */

AngularJS操作DOM——angular.element

addClass()-为每个匹配的元素添加指定的样式类名after()-在匹配元素集合中的每个元素后面插入参数所指定的内容&#xff0c;作为其兄弟节点append()-在每个匹配元素里面的末尾处插入参数内容attr() - 获取匹配的元素集合中的第一个元素的属性的值bind() - 为一个元素绑定一个事…

C++中operator的主要用法

1&#xff0e; operator 用于类型转换函数&#xff1a; 类型转换函数的特征&#xff1a; 1&#xff09; 型转换函数定义在源类中&#xff1b; 2&#xff09; 须由 operator 修饰&#xff0c;函数名称是目标类型名或目标类名&#xff1b; 3&#xff09; 函数没有参数&#x…

声纹识别

一、 声纹识别是一项根据语音波形中反映说话人生理和行为特征的语音参数&#xff0c;自动识别说话人身份的技术。与语音识别不同的是&#xff0c;声纹识别利用的是语音信号中的说话人身份信息&#xff0c;而不考虑语音中的字词意思。由于每个人的生物特征具有与其他人不同的唯一…

Asp.net mvc 实时生成缩率图到硬盘

之前对于缩率图的处理是在图片上传到服务器之后&#xff0c;同步生成两张不同尺寸的缩率供前端调用&#xff0c;刚开始还能满足需求&#xff0c;慢慢的随着前端展示的多样化&#xff0c;缩率图已不能前端展示的需求&#xff0c;所以考虑做一个实时生成图片缩率图服务。 每次调用…

数据库事务的隔离机制

数据库事务(Database Transaction) &#xff0c;是指作为单个逻辑工作单元执行的一系列操作&#xff0c;要么完全地执行&#xff0c;要么完全地不执行。----百度百科就是说你定义一组数据库操作&#xff0c;然后告诉数据库说这些操作要么都成功&#xff0c;要么都不成功。类似于…

如何使用CppUnit进行单元测试

http://www.vckbase.com/document/viewdoc/?id1762 一、前言 测试驱动开发(TDD)是以测试作为开发过程的中心&#xff0c;它坚持&#xff0c;在编写实际代码之前&#xff0c;先写好基于产品代码的测试代码。开发过程的目标就是首先使测试能够通过&#xff0c;然后再优化设计结构…

录制wav格式的音频

项目中有面部认证、声纹认证&#xff0c;服务器端要求上传wav格式的音频&#xff0c;所以写了这样一个小demo。 刚刚开始写博客还不知道怎么上传代码&#xff0c;就复制了&#xff0c;嘻嘻 DotimeManage.h class DotimeManage; protocol DotimeManageDelegate <NSObject&g…

iOS开发网络篇—Reachability检测网络状态

前言&#xff1a;当应用程序需要访问网络的时候&#xff0c;它首先应该检查设备的网络状态&#xff0c;确认设备的网络环境及连接情况&#xff0c;并针对这些情况提醒用户做出相应的处理。最好能监听设备的网络状态的改变&#xff0c;当设备网络状态连接、断开时&#xff0c;程…

网络七层协议 五层模型 TCP连接 HTTP连接 socket套接字

socket&#xff08;套接字&#xff09;是通信的基石&#xff0c;是支持TCP/IP协议的网络通信的基本操作单元&#xff0c;包含进行网络通信必须的五种信息&#xff1a;连接使用的协议&#xff0c;本地主机的IP地址&#xff0c;本地进程的协议端口&#xff0c;远地主机的IP地址&a…

[vs2010 project] CppUnit快速入门

简介 测试是软件开发过程中极其重要的一环&#xff0c;详尽周密的测试能够减少软件BUG&#xff0c;提高软件品质。测试包括单元测试、系统测试等。其中单元测试是指针对软件功能单元所作的测试&#xff0c;这里的功能单元可以是一个类的属性或者方法&#xff0c;测试的目的是看…

[javascript|基本概念|Number]学习笔记

Number类型的值&#xff1a;整数/浮点数值 整数 十进制 e.g.: var intNum 50; 八进制 (严格模式下无效,解析错误)字面值首位必须是0,之后的数字序列为0&#xff5e;7 e.g.: var intNum 070; //解析为十进制56 (如果字面值数值超出了范围&#xff0c;前导0将被忽略&#xf…