学习JavaScript设计模式(接口)_javascript技巧

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,FormItem
  var 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;
        }
      };
       //如果没有实现,则返回false
       if (!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、依赖于接口的设计模式

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

以上就是JavaScript设计模式中接口的实现相关介绍,希望对大家的学习有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript
, 接口
设计模式
,以便于您获取更多的相关知识。

时间: 2024-09-20 11:02:02

学习JavaScript设计模式(接口)_javascript技巧的相关文章

学习JavaScript设计模式(封装)_javascript技巧

在JavaScript 中,并没有对抽象类和接口的支持.JavaScript 本身也是一门弱类型语言.在封装类型方面,JavaScript 没有能力,也没有必要做得更多.对于JavaScript 的设计模式实现来说,不区分类型是一种失色,也可以说是一种解脱. 从设计模式的角度出发,封装在更重要的层面体现为封装变化. 通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易.这可以最大程

学习JavaScript设计模式(继承)_javascript技巧

1.继承 在javascript中继承是一个非常复杂的话题,比其他任何面向对象语言的中的继承都复杂得多.在大多数其他面向对象语言中,继承一个类只需要使用一个关键字即可.与它们不同,在javascript中要想达到传承公用成员的目的,需要采取一系列措施.更有甚者,javascript属于使用原型式继承的少数语言之一.利益于这种语言的灵活性,你既可使用标准的基于类的继承,也可使用更微妙一些的原型式继承. 2.为什么需要继承? 一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的

学习JavaScript设计模式(单例模式)_javascript技巧

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池.全局缓存.浏览器的window对象.在js开发中,单例模式的用途同样非常广泛.试想一下,当我们单击登录按钮的时候,页面中会出现一个登录框,而这个浮窗是唯一的,无论单击多少次登录按钮,这个浮窗只会被创建一次.因此这个登录浮窗就适合用单例模式. 1.单例模式的使用场景 在使用一种模式之前,我们最好要知道,这种模式的使用场景.用了这么久的单例模式,竟全然不知!用它

JavaScript设计模式初探_javascript技巧

目的:设计模式众多,尝试记录下学到的不同设计模式的优劣,方便以后查阅. 前言:半年前看高程的时候看到设计模式这章,云里雾里,不是看不明白,而是不明白为啥要如此麻烦只为创建一个对象.直到最近完成了自己第一个小项目,才体会到当代码量多起来时没有适当的规范与限制是多么大的灾难.于是重新翻开高程,总结下几种我学到的简单设计模式的优劣. 正文:本文一共介绍7种设计模式以及他们的应用场景.优劣. 1.工厂模式 直接用函数来封装对象,将对象作为返回值. function person (name,age) {

小议javascript 设计模式 推荐_javascript技巧

记得早前就说过要和大家分享"javascript设计模式",迟迟没写不是因为我懒,最近确实太忙,忙工作,忙旅游(啊哦?),好不容易这几天空闲了,接下来是兑现之前空口白话的时间了. 在讨论设计模式之前,请确认您已经有一定的脚本编程基础,如果不甚了解,建议可以先查阅本人很久之前写的这篇<浅谈javascript面向对象编程>请看下一篇文章. 讲到设计模式,不得不先重点着墨于"接口设计",因为接口设计在设计模式中的意义太大了,大于模式本身.直观起见,先介绍一下

轻松学习Javascript闭包函数_javascript技巧

闭包函数是什么?在开始学习的闭包的时候,大家很能都比较难理解.就从他的官方解释来说,都是比较概念化的. 不过我们也还是从闭包的含义出发. 闭包是指函数有自由独立的变量.换句话说,定义在闭包中的函数可以"记忆"它创建时候的环境. 官方解释说完后,我们先来看一个简单计数的例子. var c = 0; function count(){ c++; } count();// 1 count();// 2 这个例子是利用了全局变量来实现,但是这里有个问题是,c这个变量也容易被其他方式所调用,这时

跟我学习javascript的全局变量_javascript技巧

一.尽量少用全局对象 全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免. web页面包含不是该页面开发者所写的代码也是比较常见的,例如: 第三方的JavaScript库 广告方的脚本代码 第三方用户跟踪和分析脚本代码 不同类型的小组件,标志和按钮 比方说,该第三方脚本定义了一个全局变量,叫做result:接着,在你的函数中也定义一个名为resu

跟我学习javascript的循环_javascript技巧

1.优先使用数组而不是Object类型来表示有顺序的集合 ECMAScript标准并没有规定对JavaScript的Object类型中的属性的存储顺序. 但是在使用for..in循环对Object中的属性进行遍历的时候,确实是需要依赖于某种顺序的.正因为ECMAScript没有对这个顺序进行明确地规范,所以每个JavaScript执行引擎都能够根据自身的特点进行实现,那么在不同的执行环境中就不能保证for..in循环的行为一致性了. 比如,以下代码在调用report方法时的结果就是不确定的: f

跟我学习javascript的闭包_javascript技巧

JavaScript 闭包究竟是什么? 用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑.陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了资料也看了一些,但还是不是非常明白,最近偶然看了一下 jQuery基础教程 的附录,发现附录A对JavaScript的闭包的介绍简单易懂,于是借花献佛总结一下. 1.定义 闭包:是指有权访问另外一个函数作用域中的变量的函数.创建闭包的常见方式就是在一个函数内部创建另外一个函数. 直接上例子 function a(){ v