澳门新葡萄京官网注册JavaScript设计模式之—-接口的实现

一、什么是接口

接口是面向对象JavaScript程序员的工具箱中最有用的工具之一。在设计模式中提出的可重用的面向对象设计的原则之一就是“针对接口编程而不是实现编程”,即我们所说的面向接口编程,这个概念的重要性可见一斑。但问题在于,在JavaScript的世界中,没有内置的创建或实现接口的方法,也没有可以判断一个对象是否实现了与另一个对象相同的一套方法,这使得对象之间很难互换使用,好在JavaScript拥有出色的灵活性,这使得模拟传统面向对象的接口,添加这些特性并非难事。接口提供了一种用以说明一个对象应该具有哪些方法的手段,尽管它可以表明这些方法的含义,但是却不包含具体实现。有了这个工具,就能按对象提供的特性对它们进行分组。例如,假如A和B以及接口I,即便A对象和B对象有极大的差异,只要他们都实现了I接口,那么在A.I(B)方法中就可以互换使用A和B,如B.I(A)。还可以使用接口开发不同的类的共同性。如果把原本要求以一个特定的类为参数的函数改为要求以一个特定的接口为参数的函数,那么所有实现了该接口的对象都可以作为参数传递给它,这样一来,彼此不相关的对象也可以被相同地对待。

JavaScript设计模式之—-接口的实现

1、接口
澳门新葡萄京官网注册 ,(1)什么是接口?
接口是提供了一种用以说明一个对象应该具有哪些方法的手段。尽管它可以表明这些方法的语义,但它并不规定这些方法应该如何实现。例如,如果一个接口包含有一个名为setName的方法,那么你有理由认为这个方法的实现应该具有一个字符串参数,并且会把这个参数赋给一个name变量。有了这个工具,你就能按对象提供的特性对它们进行分组。例如,即使一批对象彼此存在着极大的差异,只要它们都实现了Comparable接口,那么在object.compare(anotherObject)方法中就可以互换使用这些对象。你还可以使用接口开发不同的类之间的共同性。如果把原本要求以一个特定的类为参数的函数改为要求以一个特定的接口为参数的函数,那么任何实现了该接口的对象都可以作为参数传递给。这样一来,彼此不相关的对象也可以被同等对待。
(2)接口之利
在面向对象的javascript中,接口有些什么作用呢?既定的一批接口具有自我描述性,并能促进代码的重用。接口可以告诉程序员一个类实现了哪些方法,从而帮助其使用这个类。如果你熟悉一个特定的接口,那么就已经知道如何使用任何实现了它的类,从而更有可能重用现有的类。接口还有助于稳定不同类之前的通信方式。如果事先知道了接口,你就能减少在集成两个对象的过程中出现的问题。借助它,你可以事先就说明你希望一个类具有哪些特性和操作。一个程序可以针对所需要的类定义一个接口,并把它转交给另一个程序。第二个程序员可以随心所欲地编写自己的代码,只要他定义的类实现了那个接口就行。这在大型项目中尤其有用。在大型项目中尤其有用。
测试和调式因此也能变得更轻松。在javascript这种弱类型语言中,类型不匹配错误很难跟踪。使用接口可以让这种错误的查找变午更容易一点,因为此时如果一个对象不像所要求的类型,或者没有实现必要的方法,那么你会得到包含有用信息的明确的错误提示。这样一来,逻辑错误可以被限制在方法自身,而不是在对象构成之中。接口还能让代码变得更稳固,因为对接口的任何改变在所有实现它的类都必须体现出来。如果接口添加了一个操作,而某个实现它的类并没有相应的添加这个操作,那么你肯定会立即见到一个错误。
(3)接口之弊
接口并没非没有缺点。javascript是一种具有极强表现图片的语言,这主要得益于其弱类型的特点。而接口的使用则一定程序上强化了类型的作用。这降低了语言的灵活性。
javascript并没有提供对接口的内置支持,而试图模仿其它语言内置的功能总会有一些风险。javascript没有interface这个关键词,因此不管你用什么方法实现接口,它总是与C++和java这些语言的方法大相径庭,这加大了初涉javascript时所遇的困难。
javascript中任何实现接口的方法都会性能造成一些影响,在某种程序上这得归咎于额外的方法调用的开销。我们的实现方法中使用了两个for循环来遍历所需要的每个接口中的每个方法。对于大型接口和需要实现许多不同接口的对象,这种检查可能要花点时间,从而对性能造成负面影响。在乎这个问题,那么
可以开必完成之后剔除这种代码,或者将其执行与一个调试标志关联起来,这样在运营环境中它就会执行。但要注意不要过早进行优化处理。firebug这类性能分析器可以帮助你判断是否真有必要剔除接口代码。
js中接口使用的最大问题在于,无法强迫其他程序员遵守你定义的接口。在其它语言中,接口的概念是内置的,如果某人定义了实现一个接口的类,那么编译器会确保该类的确实现了这个接口。而在javascript中则必须用手工的办法保证某个类实现了一个接口。编码规范和辅助类可以提供一些帮助,但无法彻底根除这个问题。如果项目的其他程序员不认真对待接口,那么这些接口的使用是无法得到强制性保证的。除非项目的所有人都同意使用接口并对其进行检查,否则接口的很多价值都无从体现。

2、在javascript中模仿接口
javascript中模仿接口的三种方法:
1、注解描述方式
2、属性检查法
3、鸭式辨型法
没有哪种技术是完美的,但三者结合使用基本上可以令人满意。
用注释描述接口
用注释模仿接口是最简单的方法,但效果却是最差的。这种方法模仿其他页面对象语言中的做法,使用了interface和implements关键字,但把它们放在注释中,以免引起语法错误。如下:

<script> //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) //2、属性检测 //3、鸭式辨型 </script>

2、属性检测法
这种方法更严谨一点。所有类都明确地声明自己实现了哪些接口,那些想与这些类打交道的对象可能针对这些声明进行检查。那些接口自身仍然只是注释,但现在你可以通过检查一个属性得知某个类自称实现了什么接口
这种模仿并不是很好。它没有为确保CompositeForm真正实现了正确的方法集而进行检查,也不会抛出错误以告知程序员程序中的问题。说到底它主要还是属于程序文档范畴。在这种做法中,对接口约定的遵守完全依靠自觉。
尽管如此,这种方法也有其优点。它易于实现,不需要额外的类或函数。它可以提高代码的可重用性,因为现丰那些类实现的的接口都有说明,程序员可以把它们与其他实现了同样接口的类互换使用。这种方法并不影响文件尺寸或执行速度,因为它所用的注释可以在对代码进行部署时,不费吹灰之图片地予以剔除。但是,由于不会提供错误消息,它对测试和调式没有什么帮助。

<script> /** * 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++) { //接收实现每一个接口的名字 var interfaceName = arguments[i]; //判断此方法到底是实现了还是失败了?规范里定义了interfaceImplments. var interfaceFound =false; for (var j = 0;j

这个例子中,CompositeForm宣称自己实现了Composite和FormItem接口,其做法是把这两个接口名称加入一个名为implementsInterfaces的数组。类显式声明自己支持什么接口。任何一个要求基于参数属于特定类型的函数都可以对这个属性进行检查,并在所需接口未在声明之列时抛出一个错误。
这种方法有几个优点。它对类所实现的接口提供了文档说明。如果需要的接口不在一个类宣称支持的接口之列,你会看到错误消息。通过利用这些错误,你可以强迫其他程序员声明这些接口。
这种方法的主要缺点在于它并未确保类真正实现了自称实现的接口。你只知道它是否说自己实现了接口。在创建一个类时声明它实现了一个接口,但后来在实现该接口所规定的方法时却漏掉其中的某一个,这种错误很常见。此时所有检查都能通过,但那个方法却不存在,这将在代码中埋下一个隐患。另外显式声明类所支持的接口也需要一些额外的工作。

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

<script> /* 实现接口的第三种方式:鸭式辨型发实现接口,(较为完美的实现方法) 核心思想:一个类实现接口的主要目的:把其中的方法都实现了(检测方法) 完全面向对象 代码实现统一,实现解耦*/ //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 <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

与另外两种方法不同,这种方法并不借助注释。其各个方面都是可以强制实施的。ensureImplements函数需要至少两个参数。第一个参数是想要检查的对象。其余参数是据以对那个对象进行检查的接口。该函数检查其第一个参数代表的对象是否实现了那些接口所声明的所有方法。如果发现漏掉了任何一个方法,它就会抛出错误,其中包含了所缺少的那个方法和未被正确实现的接口的名称等有用信息。这种检查可以用在代码中任何需要确保某个对象实现了某个接口的地方。在本例中,addForm函数仅当一个表单对象支持所有必要的方法时才会对其执行添加操作。
尽管鸭式辨型可能是上述三种方法中最有用的一种,但它也有一些缺点。这种方法中,类并不声明自己实现了哪些接口,这降低了代码的可重用性,并且也缺乏其他两种方法那样的自我描述性。它需要使用一个辅助类Interface和一个辅助函数ensureImplements。而且,它只关心方法的名称,并不检查其参数的名称、数目或类型。

4、第1种和第3种结合
我们用注释声明类支持的接口,从而提高代码的可重用性及其文档的完善性。我们还用辅助类Interface及类方法Interface.ensureImplements来对对象实现的方法进行显示检查。如果对象未能通过检查,这个访求将返回一条有用的错误消息。

从中可以看到,该类所有方法对其参数都有严格的要求,如果参数未能通过检查,将导致错误的抛出。我们特地加入这种检查的目的在于:如果没有错误抛出,那么你可以肯定接口已经得到了正确的声明和实现。
5、Interface类的使用场合
严格的类型检查并不总是明智的。许多js程序员根本不用接口或它所提供的那种检查,也照样一干多年。接口在运用设计模式实现复杂系统的时候最能体现其价值。它看似降低javascript的灵活性,而实际上,因为使用接口可以降低对象间的耦合程度,所以它提高了代码的灵活性。接口可以让函数变得更灵活,因为你既能向函数传递任何类型的参数,又能保证它只会使用那些具有必要方法的对象。
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

该类的构造器会对参数进行检查,以确保其的确为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类代码,就让那个检查变得更准确,而且更宽容。

6、依赖于接口的设计模式 l 工厂模式 l 组合模式 l 装饰模式 l 命令模式

 

1、接口
(1)什么是接口?
接口是提供了一种用以说明一个对象应该具有哪些方法的手段。尽管它可以表…

二、接口的利与弊

既定的接口具有自我描述性,并能够促进代码的重用性,接口可以提供一种信息,告诉外部一个类需要实现哪些方法。还有助于稳定不同类之间的通信方式,减少了继承两个对象的过程中出现的问题。这对于调试也是有帮助的,在JavaScript这种弱类型语言中,类型不匹配很难追踪,使用接口时,如果出现了问题,会有更明确的错误提示信息。当然接口并非完全没有缺点,如果大量使用接口会一定程度上弱化其作为弱类型语言的灵活性,另一方面,JavaScript并没有对接口的内置的支持,只是对传统的面向对象的接口进行模拟,这会使本身较为灵活的JavaScript变得更加难以驾驭。此外,任何实现接口的方式都会对性能造成影响,某种程度上归咎于额外的方法调用开销。接口使用的最大的问题在于,JavaScript不像是其他的强类型语言,如果不遵守接口的约定,就会编译失败,其灵活性可以有效地避开上述问题,如果是在协同开发的环境下,其接口很有可能被破坏而不会产生任何错误,也就是不可控性。

在面向对象的语言中,使用接口的方式大体相似。接口中包含的信息说明了类需要实现的方法以及这些方法的签名。类的定义必须明确地声明它们实现了这些接口,否则是不会编译通过的。显然在JavaScript中我们不能如法炮制,因为不存在interface和implement关键字,也不会在运行时对接口是否遵循约定进行检查,但是我们可以通过辅助方法和显式地检查模仿出其大部分特性。

三、在JavaScript中模仿接口

在JavaScript中模仿接口主要有三种方式:通过注释、属性检查和鸭式辩型法,以上三种方式有效结合,就会产生类似接口的效果。

注释是一种比较直观地把与接口相关的关键字(如interface、implement等)与JavaScript代码一同放在注释中来模拟接口,这是最简单的方法,但是效果最差。代码如下:

//以注释的形式模仿描述接口
/*
interface Composite{
    function add(child);
    function remove(child);
    function getName(index);
}

interface FormItem{
    function save();
}
*/

//以注释的形式模仿使用接口关键字
var CompositeForm =function(id , method,action) { //implements Composite , FormItem
    // do something
}
//模拟实现具体的接口方法 此处实现Composite接口
CompositeForm.prototype.Add=function(){
    // do something
}

CompositeForm.prototype.remove=function(){
    // do something
}

CompositeForm.prototype.getName=function(){
    // do something
}

//模拟实现具体的接口方法 此处实现FormItem接口
Composite.prototype.save=function(){
    // do something
}

这种方式其实并不是很好,因为这种模仿还只停留在文档规范的范畴,开发人员是否会严格遵守该约定有待考量,对接口的遵守完全依靠开发人员的自觉性。另外,这种方式并不会去检查某个函数是否真正地实现了我们约定的“接口”。尽管如此,这种方式也有优点,它易于实现而不需要额外的类或者函数,可以提高代码的可重用性,因为类实现的接口都有注释说明。这种方式不会影响到文件占用的空间或执行速度,因为注释的代码可以在部署的时候轻松剔除。但是由于不会提供错误消息,它对测试和调试没什么帮助。下面的一种方式会对是否实现接口进行检查,代码如下:

//以注释的形式模仿使用接口关键字
var CompositeForm =function(id , method,action) { //implements Composite , FormItem
    // do something
    this.implementsinterfaces=['Composite','FormItem']; //显式地把接口放在implementsinterfaces中
}

//检查接口是否实现
function implements(Object){
    for(var i=0 ;i< arguments.length;i++){
        var interfaceName=arguments[i];
        var interfaceFound=false;
        for(var j=0;j<Object.implementsinterfaces.length;j++){
            if(Object.implementsinterfaces[j]==interfaceName){
                interfaceFound=true;
                break;
            }
        }
        if(!interfaceFound){
            return false;
        }else{
            return true;
        }
    }
}

function AddForm(formInstance){
    if(!implements(formInstance,'Composite','FormItem')){ 
        throw new Error('Object does not implements required interface!');
    }
}

上述代码是在方式一的基础上进行完善,在这个例子中,CompositeForm宣称自己实现了Composite和FormItem这两个接口,其做法是把这两个接口的名称加入一个implementsinterfaces的数组。显式地声明自己支持什么接口。任何一个要求其参数属性为特定类型的函数都可以对这个属性进行检查,并在所需要的接口未在声明之中时抛出错误。这种方式相对于上一种方式,多了一个强制性的类型检查。但是这种方法的缺点在于它并未保证类真正地实现了自称实现的接口,只是知道它声明自己实现了这些接口。其实类是否声明自己支持哪些接口并不重要,只要它具有这些接口中的方法就行。鸭式辩型(像鸭子一样走路并且嘎嘎叫的就是鸭子)正是基于这样的认识,它把对象实现的方法集作为判断它是不是某个类的实例的唯一标准。这种技术在检查一个类是否实现了某个接口时也可以大显身手。这种方法的背后观点很简单:如果对象具有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。可以使用一个辅助函数来确保对象具有所有必需的方法,代码如下:

//interface
var Composite =new Interface('Composite',['add','remove','getName']);
var FormItem=new Interface('FormItem',['save']);

//class
var Composite=function(id,method,action){

}

//Common Method
function AddForm(formInstance){
    ensureImplements(formInstance,Composite,FormItem);
    //如果该函数没有实现指定的接口,这个函数将会报错
}

与另外两种方式不同,这种方式无需注释,其余的各个方面都是可以强制实施的。EnsureImplements函数需要至少两个参数。第一个参数是想要检查的对象,其余的参数是被检查对象的接口。该函数检查器第一个参数代表的对象是否实现了那些接口所声明的方法,如果漏掉了任何一个,就会抛错,其中会包含被遗漏的方法的有效信息。这种方式不具备自我描述性,需要一个辅助类和辅助函数来帮助实现接口检查,而且它只关心方法名称,并不检查参数的名称、数目或类型。

四、Interface类

在下面的代码中,对Interface类的所有方法的参数都进行了严格的控制,如果参数没有验证通过,那么就会抛出异常。加入这种检查的目的就是,如果在执行过程中没有抛出异常,那么就可以肯定接口得到了正确的声明和实现。

var Interface = function(name ,methods){
    if(arguments.length!=2){
        throw new Error('2 arguments required!');
    }
    this.name=name;
    this.methods=[];
    for(var i=0;len=methods.length;i<len;i++){
        if(typeof(methods[i]!=='String')){
            throw new Error('method name must be String!');
        }
        this.methods.push(methods[i]);
    }
}

Interface.ensureImplements=function(object){
    if(arguments.length<2){
        throw new Error('2 arguments required at least!');
    }
    for(var i=0;len=arguments.length;i<len;i++){
        var interface=arguments[i];
        if(interface.constructor!==Interface){
            throw new Error('instance must be Interface!');
        }
        for(var j=0;methodLength=interface.methods.length;j<methodLength;j++){
            var method=interface.methods[j];
            if(!object[method]||typeof(object[method])=='function')){
                throw new Error('object does not implements method!');
            }    
        }
    }
}

其实多数情况下,接口并不是经常被使用的,严格的类型检查并不总是明智的。但是在设计复杂的系统的时候,接口的作用就体现出来了,这看似降低了灵活性,却同时也降低了耦合性,提高了代码的重用性。这在大型系统中是比较有优势的。在下面的例子中,声明了一个displayRoute方法,要求其参数具有三个特定的方法,通过Interface对象和ensureImplements方法来保证这三个方法的实现,否则将会抛出错误。

//声明一个接口,描述该接口包含的方法
 var DynamicMap=new Interface{'DynamicMap',['centerOnPoint','zoom','draw']};

 //声明一个displayRoute方法
 function displayRoute(mapInstance){
    //检验该方法的map
    //检验该方法的mapInsstance是否实现了DynamicMap接口,如果未实现则会抛出
    Interface.ensureImplements(mapInstance,DynamicMap);
    //如果实现了则正常执行
    mapInstance.centerOnPoint(12,22);
    mapInstance.zoom(5);
    mapInstance.draw();
 }

下面的例子会将一些数据以网页的形式展现出来,这个类的构造器以一个TestResult的实例作为参数。该类会对TestResult对象所包含的数据进行格式化(Format)后输出,代码如下:

var ResultFormatter=function(resultObject){
     //对resultObject进行检查,保证是TestResult的实例
     if(!(resultObject instanceof TestResult)){
         throw new Error('arguments error!');
     }
     this.resultObject=resultObject;
 }

 ResultFormatter.prototype.renderResult=function(){
     var dateOfTest=this.resultObject.getData();
     var resultArray=this.resultObject.getResults();
     var resultContainer=document.createElement('div');
     var resultHeader=document.createElement('h3');
     resultHeader.innerHTML='Test Result from '+dateOfTest.toUTCString();
     resultContainer.appendChild(resultHeader);

     var resultList=document.createElement('ul');
     resultContainer.appendChild(resultList);

     for(var i=0;len=resultArray.length;i<len;i++){
         var listItem=document.createElement('li');
         listItem.innerHTML=resultArray[i];
         resultList.appendChild('listItem');
     }
     return resultContainer;
 }

该类的构造器会对参数进行检查,以确保其的确为TestResult的类的实例。如果参数达不到要求,构造器将会抛出一个错误。有了这样的保证,在编写renderResult方法的时候,就可以认定有getData和getResult两个方法。但是,构造函数中,只对参数的类型进行了检查,实际上这并不能保证所需要的方法都得到了实现。TestResult类会被修改,致使其失去这两个方法,但是构造器中的检查依旧会通过,只是renderResult方法不再有效。

此外,构造器中的这个检查施加了一些不必要的限制。它不允许使用其他的类的实例作为参数,否则会直接抛错,但是问题来了,如果有另一个类也包含并实现了getData和getResult方法,它本来可以被ResultFormatter使用,却因为这个限制而无用武之地。

解决问题的办法就是删除构造器中的校验,并使用接口代替。我们采用这个方案对代码进行优化:

//接口的声明
var resultSet =new Interface('ResultSet',['getData','getResult']);

//修改后的方案
 var ResultFormatter =function(resultObject){
     Interface.ensureImplements(resultObject,resultSet);
     this.resultObject=resultObject;
 }

上述代码中,renderResult方法保持不变,而构造器却采用的ensureImplements方法,而不是typeof运算符。现在的这个构造器可以接受任何符合接口的类的实例了。

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

<1>工厂模式:对象工厂所创建的具体对象会因具体情况而不同。使用接口可以确保所创建的这些对象可以互换使用,也就是说对象工厂可以保证其生产出来的对象都实现了必需的方法;

<2>组合模式:如果不使用接口就不可能使用这个模式,其中心思想是可以将对象群体与其组成对象同等对待。这是通过接口来做到的。如果不进行鸭式辩型或类型检查,那么组合模式就会失去大部分意义;

<3>装饰者模式:装饰者通过透明地为另一个对象提供包装而发挥作用。这是通过实现与另外那个对象完全一致的接口实现的。对于外界而言,一个装饰者和它所包装的对象看不出有什么区别,所以使用Interface来确保所创建的装饰者实现了必需的方法;

<4>命令模式:代码中所有的命令对象都有实现同一批方法(如run、ecxute、do等)通过使用接口,未执行这些命令对象而创建的类可以不必知道这些对象具体是什么,只要知道他们都正确地实现了接口即可。借此可以创建出模块化程度很高的、耦合度很低的API。

发表评论

电子邮件地址不会被公开。 必填项已用*标注