设计API时要区分其目标用户群8第八章插件、客户端程序的区别:插件:插件是一种遵循一定规范的应用程序接口编写出来的程序。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。客户端程序:客户服务(CustomerService),是指一种以客户为导向的价值观,它整合及管理在预先设定的最优成本——服务组合中的客户界面的所有要素。广义而言,任何能提高客户满意度的内容都属于客户服务的范围之内。一些程序功能不仅可以直接通过界面进行操作,还可以提供相应的API,以供其他程序来调用,这样第三方程序可以发起通讯,使用相应程序的API指使它完成操作,命令执行完后,控制权返回给调用者。XMMS的API还提供插件支持,第三方可发商可以通过注册“输出插件”来扩展XMMS的功能。它与前面说的客户端的地位完全不同。第三方的播放场SPI组成:是与前面说的客户端的地位完全不同。它没有让XMMS进行任何操作。但它为XMMS增加了很多功能,所以此时的插件不是前文所说的“客户端”。XMMS的这种注册插件来扩展功能的方式是SPI(ServiceProviderInterface)的典型应用。ServiceProviderInterface为了说明客户端API和SPI区别,Voidxmms_play();Voidxmms_pause();Voidxmms_add_to_list(char*);使用Java语言来定义API的方式则完全不同。PublicclassXMMS{Publicvoidplay(){doPlay();}Publicvoidpause(){doPause();}PublicvoidaddToPlaylist(Stringfile){doaddToPlaylist(file);}}8.1C和Java语言中如何定义API和SPI(1)C和Java语言中如何定义API和SPI(2)在Java中声明API方式有多种。如说可使用static方法、实例方法、抽象方法、以及final方法,这些都是可以的。但编写一个SPI,情况就不同。如果要使用C语言来为XMMS编写程序一个插件的话,必须要写一个支持回放的函数,代码如下:Voidmy_playbackprints(char*text){Printf(“%s\n”,text);}InterfacePlayback{Publicvoidplayback(byte[]data);}ClassMyCallbackPrintsimplementsXMMS.Playback{Publicvoidplayback(byte[]data){System.out.println(newString(data));}}Xmms.registerPlayback(newMyCallbackPrints());C和Java语言中如何定义API和SPI(3)对于Java程序员,只要所编写的方法不是private、final或者static的,那么就表示该是方法支持调的,而且可以用看成是一个API。对很多程序员,甚至是教师都没有清楚地理解这一点,它也的确不是日常编码的内容。8.2API演进不同于SPI演进(1)具体的演进方案则还是取决于具体的接口类型:向API中添加一些内容总是可以的,但要移除一些内容则不行。但对于SPI,移除一些内容可以允许,但不允许添加新的内容。建立契约时,必须清楚地区分哪些是API,提供给外面调用,而哪些是SPI,用来让外部来扩展程序功能。设计时容易犯的最大错误就是将API和SPI混在一个类里。根据SPI的契约,不能添加方法,根据API的契约,也不能减少方法。8.3java.io.Write这个类从JDK1.4到JDK5的演进还需要使用try/catch/finally的方式来进行防御性编码。Try{bufferedWrite.append(what);}catch(UnsupportedOpeartionExceptionex){bufferedWrite.write(what.toString());}这样来调用API的确有点怪。调用一个方法竟然要写四行代码java.io.Write的演进(2)假设已有一个优化过Write类,它无须将数据转换成字符串,而是直接处理以提高性能,但用户却可能无法用到这种梦寐以求的优化,PublicclassCountingWriteextendsWrite{Privateintcounter;PublicintgetcharacterCount(){Returncounter;}java.io.Write的演进(3)@OverridePublicvoidwrite(char[]cbuf,intoff,intlen)throwsIoException{Counter+=len;}@OverridePublicWriteappend(CharSepuencecsq)throwsIoException{Counter+=csq.length();Returnthis;}。PrivatestaticfinalclassCDSequenceimplementsCharSequence{Privatefinalintstart;Privatefinalintend;PublicCDSequence(){This(0,647*1024*1024);}PrivateCDSequence(intstart,intend){This.start=start;This.end=end;}Publicintlength(){Returnend-start;}}CountingWriterwriter=newCountingWriter();CDSequencecdImage=newCDSequence();BufferedWriterbufferedWriter=newBufferedWriter(writer);bufferedWriter.append(cdImage);assertEquals(“Correctnumberofwritesdelegated”,cdImage.length(),writer.getCharacterCount());从JDK1.4到JDK5的演进(4)这个通过覆盖来优化性能的方法却不会被调用,效率仍然没有提高。If(shouldBufferAsTheSequenceIsNotTooBig(csq)){Write(csq.toString());}else{Flush();Out.append(csq);}Returnthis;从JDK1.4到JDK5的演进(5)首先计算一下字符的数量,如果数量比较小,性能影响也比较小,就使用老的方式处理,否则就先写入当前数据后,再写入大数据。然而,还远远没有结束,还有一个潜在的问题需要解决。PublicclassCryptoWriterextendsBufferedWriter{PublicCryptoWriter(Writerout){Super(out);}@OverridePublicvoidwrite(char[]buf,intoff,intlen)throwsIoException{Char[]arr=newchar[len];For(inti=0;ilen;i++){Arr[i]=encryptChar(buf[off+i]);}Super.write(arr,0,len);}从JDK1.4到JDK5的演进(6)@OverridePublicvoidwrite(intc)throwsIoException{Super.write(encryptChar©);}@overridePublicvoidwrite(Stringstr,intoff,intlen)throwsIoException{StringBuffersb=newStringBuffer();For(inti+0;ilen;I++){Sb.append(encryptChar(str.charAt(off+i)));}Super.write(sb.toString(),0,len);}PrivatecharencryptChar(intc){If(c==‘Z’){Return‘A’;}If(c==’z’){Return‘a’;}Return(char)(c+1);}}。从JDK1.4到JDK5的演进(7)事情变得越来越复杂了!为了处理含有大量字符串的对象等目的,最好是直接将数据写入相应的Writer对象。BooleanisOverriden=false;Try{isOverriden=(getClas().getMethod(“write”,String.class).getDeclaringClass()!=Writer.class)||(getClass().getMethod(“write”,integer.TYPE).getDeclaringClass!=BufferedWriter.class)||(getClass().getMethod(“write”,String.class,Integer.TYPE,Integer.TYPE).getDeclaringClass()!=BufferedWriter.class);}catch(Exceptionex){ThrowsnewIoException(ex);}If(isOverriden||shouldBufferAsTheSequenceIsNotTooBig(csq)){Write(csq.toString());}else{Flush();Out.append(csq);}Returnthis;}反射技术第三个版本才是最终的解决方案。它使用了反射技术,所以看起来复杂一点,但却是一个非常有效的解决方案。反射技术让这段代码看起来不如原来那么漂亮了,但至少它可以正常运行,结果也是正确的。让一个类可以被动继承,其实就意味着把它当作一个SPI,因为其子类可以提共新技术部的功能,或者说新的服务,但对于代理来说,它更多是让其它的代码来进行调用它的功能,属于功能API,两者是不一样。不能像表面看起来那么简单、顺畅了。如何避免继承(1)PublicfinalclassWriter{PrivatefinalImplimpl;PrivateWriter(Impl,impl){This.impl=impl;}Publicfinalvoidwrite(intc)throwsIoException{Char[]arr={(char)c};Impl.write(arr,0,1);}Publicfinalvoidwrite(charcbuf[])thrpwsIoException{Impl.write(cbuf,0,cbuf.length);}Publicfinalvoidwrite(charcbuf[],intoff,intlen)ThrowsIoException{Impl.write(cbuf,off,len);}Publicfinalvoidwrite(Stringstr)throwsIoException{Impl.write(str,0,str.length());}如何避免继承(2)Publicfinalvoidwrite(Stringstr,intoff,intlen)ThrowsIoException{Impl.write(str,off,len);}Publicfinalvoidflush()throwsIoException{Impl.flush();}Publicfinalvoidclose()throwsIoException{Impl.close();}SPI的提供者类(1)现在写一个SPI的提供者类,这个类中全部都是工厂方法,用来提供Impl接口的具体实现,基本上就是下面这个样子。PublicstaticWri