建构自己的java库.docx
《建构自己的java库.docx》由会员分享,可在线阅读,更多相关《建构自己的java库.docx(11页珍藏版)》请在冰豆网上搜索。
![建构自己的java库.docx](https://file1.bdocx.com/fileroot1/2023-2/1/55841b1f-a32a-4255-a424-13d681964055/55841b1f-a32a-4255-a424-13d6819640551.gif)
建构自己的java库
第一章:
教程提示
代码重用是计算机编程的一个神圣目标。
编写可轻松重用的代码是非常难的技术,但这种技术并非不可掌握。
在本教程中,您将会学到:
Java语言如何能帮助您建立一个好的、可重用的库好的库设计的一些主要原则用Java语言实现这些构思的最有效方法
为说明这些构思,我们将设计一个简单的库。
要成功使用本教程,需要基本了解Java编程,包括能够创建、编译和执行简单的命令行Java程序。
目标
学完本教程之后,您将:
知道代码重用的主要障碍了解应用于库设计思想的知识知道如何用Java语言实现这些构思可以设计一个精良的Java库,以用作将来项目的样本。
第二章:
库简介
什么是库
库就是一个可重用软件组件,它通过提供到执行某个编程任务的代码的访问,来节省开发人员的时间。
库有助于完成许多不同类型的任务。
库设计是比较难的。
使用已编写的算法并将它称作库,这当然很简单;然而要结构化此代码以使它能够适应其他人的程序,并且执行其设计时设定的任务,同时不干扰原始程序的操作,这就很难了。
大多数现代语言都尝试帮助程序员创建好的库,Java语言也不例外。
在本教程中,您将会学到Java语言如何能帮助您建立一个好的、可重用的库。
第三章:
设计问题之封装
什么是封装
库应该是一种紧凑的自适应单元,而不是一种功能和关系不明确的对象的松散集合。
使库变成自适应的操作被称作封装。
[编辑]
什么是包
Java语言为类文件级别封装提供了一个显式机制:
包。
包是一组存储在一个单独目录中的Java类;包有它自己的名称空间。
给一组类其自己的名称空间的优点是不必担心名称空间冲突。
在这个示例中,主类有一个相当普通的名称--Server。
如果我们最后遇到另一个库中同名的类,不要感到惊奇。
将类放到它自己的名称空间中可以解决由于名称引起的冲突。
在以后几屏中,我将说明如何将一个类放入包中。
[编辑]
包具有名称
每个包都有一个名称,这个名称由一组用点分隔的字符串组成,如java.lang或javax.swing.plaf.basic。
实际上,任何类的全名都包含了它的包名称,后面跟着它自己的名称,如java.lang.Object或javax.swing.plaf.basic.BasicMenuBarUI。
请注意,有一个特殊包称作缺省包。
如果没有将类放到某个特定包中,那么会假设这个类在缺省包中。
[编辑]
包名称对应于目录
每个包都直接映射到文件系统中的一个子目录。
这种对应关系让Java虚拟机(JVM)可以在运行时找到类。
通过将点替换成"/"(或者操作系统用于分隔目录名的符号),可以将包名称转换成子目录路径。
例如,java.lang.Object存储在文件java/lang/Object.java中。
缺省包中的类被放置在当前目录中。
[编辑]
类声明了它们的包
为确保类在正确的包和正确的目录中,类必须声明它所在的包。
声明如下:
//Server.java
packagemylib;
publicclassServerimplementsRunnable{
//...
其它类可以导入包
如果要使用另一个包中的类,可以利用其全名来引用它,如mylib.Server。
例如:
mylib.Serverserver=newmylib.Server(portNum);
输入类的完整包名称可能很麻烦,因此可以使用快捷方式,按以下方式导入包:
importmylib.*;
//...
Serverserver=newServer(portNum);
还可以导入单个类:
importmylib.Server;
//...
Serverserver=newServer(portNum);
选择公有类
Java语言还可以让您决定在包外部可以看见包中的哪些类。
公有类可以被任何其它包中的代码访问,而私有类只能在其自己的包中使用。
只应该使那些需要人们直接使用的类成为公有类。
为公有类设计接口需要比为私有类设计接口更小心,因为必须精心设计公有类的接口,以使用户可以清楚地了解这个接口。
私有类则不必有一个清楚的接口。
要使类成为公有类,需要在类声明的第一行中使用public关键字:
//Server.java
packagemylib;
importjava.io.*;import.*;
publicclassServerimplementsRunnable{
要使类成为私有类,只要去掉public关键字。
在Server示例中,在称为Reporter的相同包中有一个私有类,它定期报告Server对象的情况。
以下就是它的声明:
//Reporter.java
packagemylib;
classReporterimplementsRunnable{
[编辑]
封装总结
可以看到,Java语言提供了一些特别为定义代码边界而创建的功能。
通过将代码划分到各个包中,并将类定义成公有类或私有类,可以确切地决定当用户使用库时他们必须处理什么问题。
第四章:
设计问题之可扩展性
继承
封装定义了代码段周围的边界。
所有面向对象编程语言都提供了一种机制,以扩展代码段而不破坏这些边界。
在Java语言中,这个机制由继承提供。
[编辑]
通过继承定制
示例库中的主类称作Server。
如果查看这个类的源代码,就会发现这个类本身什么功能也没有。
主循环(在单独线程中运行)侦听入网连接。
当进入一个连接时,它就将这个连接转交给一个称作handleConnection()的方法,如下所示:
//subclassmustsupplyanimplementation
abstractpublicvoidhandleConnection(Sockets);
由于没有缺省实现,因此这个类被声明成abstract,这表示用户应提供实现。
EchoServer实现了这个方法:
//ThisiscalledbytheServerclasswhenaconnection
//comesin."in"and"out"comefromtheincomingsocket
//connection
publicvoidhandleConnection(Socketsocket){
try{
InputStreamin=socket.getInputStream();
OutputStreamout=socket.getOutputStream();
//justcopytheinputtotheoutput
while(true)
out.write(in.read());
}catch(IOExceptionie){
System.out.println(ie);
}
}
可以将这个过程看作是一种定制:
完成大部分工作的类(Server)还不完整;子类(EchoServer)通过添加方法handleConnection()的实现来完善它。
[编辑]
考虑特殊情况
应该事先考虑库的用户想要执行的定制类型。
前一个示例很明显--必须提供一种方法以使子类能够真正使用入网连接,否则程序不产生任何作用。
但如果没有事先考虑,那么其它一些定制可能不提供给您。
相反,这些定制将提供给用户,用户希望您会事先考虑到这些情况。
在样本库中,Server中有一个方法叫作cleanUp()。
这个方法由服务器的后台线程在退出之前调用。
基本服务器不需要在这个方法中执行任何操作,因此它是空的:
//Putanylast-minuteclean-upstuffinhere
protectedvoidcleanUp(){
}
注:
不要将这个方法声明成abstract,因为这会要求用户实现此方法,而在某些情况下,没有必要这样做。
第五章:
设计问题之调试信息
为什么要计划调试
假设:
您的库很完美。
用户只需要学习API和使用代码就可以了。
对吗?
错。
这种假设决不会(或者很少会)发生。
错误不可避免;调试必不可少。
某些情况下,用户会遇到问题,他们需要知道库中到底在发生什么。
错误可能是库本身引起的,也可能是用户的代码中的错误,而这个错误只有在库内部才会引发。
[编辑]
一小段文本有很大帮助
如果您的库附带了源代码,那么用户可能会考虑使用调试器,但不应该指望用户会那样做。
解决这个不可避免的问题的更好方法是将调试语句println()添加到代码中,以强迫每一段代码都报告它在做什么。
查看此信息通常可以帮助用户了解在什么位置出了错。
以下示例演示了这个技术。
用户的代码通过调用静态方法Server.setDebugStream(),能够安装PrintStream对象。
实现之后,调试信息将被发送到已提供的流。
//setthistoaprintstreamifyouwantdebuginfo
//senttoit;otherwise,leaveitnull
staticprivatePrintStreamdebugStream;
//callthistosendthedebuggingoutputsomewhere
staticpublicvoidsetDebugStream(PrintStreamps){
debugStream=ps;
}
然后可以通过调用debug()方法来打印库的调试信息:
//senddebuginfototheprintstream,ifthereisone
staticpublicvoiddebug(Strings){
if(debugStream!
=null)
debugStream.println(s);
}
第六章:
源代码
样本库
在结束之前,先看一下完整的源代码,它包括了EchoServer、mylib.Server和mylib.Reporter。
EchoServer
//$Id$
importjava.io.*;import.*;importmylib.*;
publicclassEchoServerextendsServer{
publicEchoServer(intport){
//Thesuperclassknowswhattodowiththeportnumber,we
//don'thavetocareaboutit
super(port);
}
//ThisiscalledbytheServerclasswhenaconnection
//comesin."in"and"out"comefromtheincomingsocket
//connection
publicvoidhandleConnection(Socketsocket){
try{
InputStreamin=socket.getInputStream();
OutputStreamout=socket.getOutputStream();
//justcopytheinputtotheoutput
while(true)
out.write(in.read());
}catch(IOExceptionie){
System.out.println(ie);
}
}
protectedvoidcleanUp(){
System.out.println("Cleaningup");
}
staticpublicvoidmain(Stringargs[])throwsException{
//Grabtheportnumberfromthecommand-line
intport=Integer.parseInt(args[0]);
//Havedebugginginfosenttostandarderrorstream
Server.setDebugStream(System.err);
//Createtheserver,andit'supandrunning
newEchoServer(port);
}
}
mylib.Server
//$Id$
packagemylib;
importjava.io.*;import.*;
abstractpublicclassServerimplementsRunnable{
//theportwe'llbelisteningon
privateintport;
//howmanyconnectionswe'vehandled
intnumConnections;
//theReporterthat'sreportingonthisServer
privateReporterreporter;
//setthistotruetotellthethreadtostopaccepting
//connections
privatebooleanmustQuit=false;
publicServer(intport){
//remembertheportnumbersothethreadcan
//listenonit
this.port=port;
//theconstructorstartsabackgroundthread
newThread(this).start();
//andstartareporter
reporter=newReporter(this);
}
//thisisourbackgroundthread
publicvoidrun(){
ServerSocketss=null;
try{
//getreadytolisten
ss=newServerSocket(port);
while(!
mustQuit){
//giveoutsomedebugginginfo
debug("Listeningon"+port);
//waitforanincomingconnection
Sockets=ss.accept();
//recordthatwegotanotherconnection
numConnections++;
//moredebugginginfo
debug("Gotconnectionon"+s);
//processtheconnection--thisisimplemented
//bythesubclass
handleConnection(s);
}
}catch(IOExceptionie){
debug(ie.toString());
}
debug("Shuttingdown"+ss);
cleanUp();
}
//thedefaultimplementationdoesnothing
abstractpublicvoidhandleConnection(Sockets);
//tellthethreadtostopacceptingconnections
publicvoidclose(){
mustQuit=true;
reporter.close();
}
//Putanylast-minuteclean-upstuffinhere
protectedvoidcleanUp(){
}
//everythingbelowprovidesasimpledebugsystemfor
//thispackage
//setthistoaprintstreamifyouwantdebuginfo
//senttoit;otherwise,leaveitnull
staticprivatePrintStreamdebugStream;
//wehavetwoversionsofthis...
staticpublicvoidsetDebugStream(PrintStreamps){
debugStream=ps;
}
//...justforconvenience
staticpublicvoidsetDebugStream(OutputStreamout){
debugStream=newPrintStream(out);
}
//senddebuginfototheprintstream,ifthereisone
staticpublicvoiddebug(Strings){
if(debugStream!
=null)
debugStream.println(s);
}
}
mylib.Reporter
//$Id$
packagemylib;
classReporterimplementsRunnable{
//theServerwearereportingon
privateServerserver;
//ourbackgroundthread
privateThreadthread;
//setthistotruetotellthethreadtostopaccepting
//connections
privatebooleanmustQuit=false;
Reporter(Serverserver){
this.server=server;
//createabackgroundthread
thread=newThread(this);
thread.start();
}
publicvoidrun(){
while(!
mustQuit){
//dothereporting
Server.debug("serverhashad"+server.numConnections+"connections");
//thenpauseawhile
try{
Thread.sleep(5000);
}catch(InterruptedExceptionie){}
}
}
//tellthebackgroundthreadtoquit
publicvoidclose(){
mustQuit=true;
}
}