分享
 
 
 

Java 库的建立方法及其实例

王朝java/jsp·作者佚名  2006-12-16
窄屏简体版  字體: |||超大  

正文:

任何一种面向对象语言都有它的库。任何一种面向对象的语言也都离不开库的支持。用我们熟悉的 面向对象语言为例子,C++有STL,Java有API函数,具体到开发工具,Visual C++提供了MFC, Borland C++提供了OWL。也有很多第三方提供的库。我们在开发应用程序的时候,也发觉我们也 许需要某些特定的库来完成特定的功能。那么,如何编写自己的库呢?

利用Java的面向对象特性,如封装,继承,和一些设计模式,我们可以用标准的方法来建立自己的 库。需要明白的一点:在你需要完成某个功能的时候,不要用专有的、特定的方法去编写代码,而 要全盘考虑,用通用的方法来完成,这样,在积累了一定数量的库以后,你就能重用这些库来完成 新的功能,而不用每回都重头编写代码。这也是面向对象语言提供给我们的好处。也可以用J2EE的 规范为例子,J2EE提供了一个CBT(Component Based Transaction),所有的组件都尊崇J2EE规范,在 CBT中运行,这样,编写开发并且重用标准的通用的组件库,可以缩短开发周期节约成本,并且可 以在任何符合J2EE规范的应用程序服务器(APPLICATION SERVER)中运行,并且可以继承,扩展已 有的组件库完成新的任务或者适应新的变化。

在本文中,我将先讨论如何建立自己的库,需要根据哪些标准,然后给出一个简单的例子。在第二 部分中,我将通过一个功能比较完善的库来做进一步的讨论。

什么是库?库是一个可以重用的组件,它采用通用的设计,完成通用的任务,可以节约开发者的时 间,缩短开发周期节约开发成本。一个设计完善的库,并不只是为了完成某一个特定的任务,而是 可以完成各种不同的任务。设计一个库是困难的。写一个算法并不难,但是设计库的时候需要一种 比较好的结构,它能够被用在各种需要的环境下,完成各种不同的任务,但是还不能影响使用它的 程序代码结构。

为什么要重用代码?重头开发一个新的软件,工作量是非常巨大的,不论你用什么工具什么语言。 而代码重用能够节约大部分时间,而把时间花在新的功能的开发上。从一定的意义上来说,写一个 新的软件是利用了现有的代码,重新拼装以实现新的功能。从另外一个角度上来讲,即使你没有打 算把你写的代码变成一个通用的库并分发给其他人使用,从设计的角度来讲,采用一种全盘的通用 的设计方法也能让你对所要完成的任务有更好的理解,并且优化你的设计过程,从而优化你的代码 结构。

采用开发库并且让别人来使用它的方式,能够帮助你在使用它的时候发现它的设计上的缺陷或者代 码中的错误,并帮助你改正它。比方说,你写了一个库让别人来使用,你不得不考虑通用的设计, 因为你并不能预见别人将在什么环境下使用和使用的目的。在其他人使用你的库的过程中,可能会 遇到一些问题,有的可能是你的文档写得不够清楚明白,有的也可能是你程序上的错误,也有可能 是使用者觉得在结构上使用起来不方便或者不正确。那么你可以继续作一些修改工作,在保持结构 和接口不变化的情况下,做一些调整。

在设计库的时候,你需要以一个使用者的眼光来看问题,考虑如何设计和实现它。你需要明白,

1、需要解决的问题是什么?需要达到一个什么目的?

2、使用者关心的问题是什么?使用者需要得到一个什么结果?

3、使用者不需要关心的问题是什么?什么细节是可以对使用者隐藏的?

下面,我们用一个简单的例子来说明如何设计和实现一个有用处的库。

设计一个网络服务程序,我们需要考虑几点:

1、监听一个端口

2、接受连接

3、读取或者写入连接的流

4、处理输入的数据,并且返回一个结果

对于我们将要实现的库来说,需要完成的是前三点,而最后一点我们留给使用者去实现,这也是使 用者需要完成和关心的地方。

库的主要类叫做Server, 测试的类叫做EchoServer. EchoServer实现了一个简单的服务,从客户端读 取数据,并且返回同样的数据。

设计原则一:封装

一个好的库必须是一个紧凑的关系紧密的整体,而不是一个分散的关系松散的对象的集合。

package是Java提供的一种类库的封装机制。一个package是一个Java类文件的集合,存放在同一个目 录中。package有专有的名字空间。

专有的名字空间的一个好处是,你不用担心名称的冲突。因为,如果你的类的名称和别人的类的名 称冲突,但是他们不在同一个package中,利用这一点可以避免名字的冲突。

每一个package都有一个字符串来代表,比如java.lang, 或者javax.swing.plaf.basic.实际上每一个类的 全名都是由package的名字加上类的名字来代表的,这样就避免了名字的冲突,比 如,java.lang.Object或者javax.swing.plaf.basic.BasicMenuBarUI.

注意,有一个特殊的package叫做default package。如果你不声明你的类属于任何一个package,那么 它就被假定属于default package.

每一个package的名字都对应一个目录。比如,java.lang.Object 存放在java/lang/Object.java中,每一 个.对应一个/. default package存放的目录是当前目录。

声明一个package.

// Server.java

package mylib;

public class Server implements Runnable

{

// ...

如果有import语句,必须放在package语句的后面。

当然你也可以引入别的package. 例如:

import mylib.Server;

// ...

Server server = new Server( portNum );

Java允许你决定package中的哪些类对外部是可见的。public类可以被包外的代码使用,而private类 则不行。

比如,让Server类能被外部的代码使用:

// Server.java

package mylib;

import java.io.*;

import java.net.*;

public class Server implements Runnable

{

如果你不想让类被外部的代码使用,可以用缺省的属性,去掉public. 例如:

// Reporter.java

package mylib;

class Reporter implements Runnable

{

设计原则二:继承

在我们的例子中,Server是主要的类。如果你看这个类的代码,就能看到,它本身其实什么也不 做。主循环用来监听连接。当连接建立以后,它把处理连接的任务交给一个叫做handleConnection() 的函数。

// subclass must supply an implementation

abstract public void handleConnection( Socket s );

因为没有实现这一函数,所以这个类被声明为abstract,使用者必须实现这个函数。

// This is called by the Server class when a connection

// comes in. "in" and "out" come from the incoming socket

// connection

public void handleConnection( Socket socket ) {

try {

InputStream in = socket.getInputStream();

OutputStream out = socket.getOutputStream();

// just copy the input to the output

while (true)

out.write( in.read() );

} catch( IOException ie ) {

System.out.println( ie );

}

}

可以说,这一继承的过程叫做定制。因为在Server类中,并没有定义该函数的动作,而是把这个定 义的过程留给使用者,让他们来完成所需要的特定的功能。

另外一个定制函数:cleanUp().

在设计类的时候,往往你能考虑到使用者需要的功能,例如上面的handleConnection().但是,也需要 考虑另外一种定制,例如在这里,在Server退出后台运行方式的时候,调用了这个cleanUp()函数, 在Server类中的实现为空,什么都不做,这把机会留给使用者,使用者可以用这个函数来做一些清 除工作,这种函数也可以称之为"钩子"。

设计原则三:调试

没有人能够做到写出一个绝对完美的程序,没有任何的错误。所以,调试是不可缺少的。有时候, 使用者可能会遇到一个问题,从而需要知道在库的代码中发生了什么问题。这个错误可能是库代码 的问题,也可能是使用者的代码在库代码中引起的问题。

如果你提供了库的源代码,使用者可以用debugger来调试错误。但是,你不能完全依赖于调试器。 在库代码中加入打印调试信息的语句,是一个好习惯。它可以帮助使用者明白,什么地方发生了错 误。

下面的例子说明了这一技术。使用者的代码使用Server.setDebugStream(),指定一个PrintStream对 象。然后,调试信息就被输出到这个流中。

// set this to a print stream if you want debug info

// sent to it; otherwise, leave it null

static private PrintStream debugStream;

// call this to send the debugging output somewhere

static public void setDebugStream( PrintStream ps ) {

debugStream = ps;

}

当使用者使用了调试的流的时候,你的库代码可以打印错误:

// send debug info to the print stream, if there is one

static public void debug( String s ) {

if (debugStream != null)

debugStream.println( s );

}

下面,来完整的看一看这个具体的例子:

EchoServer

// $Id$

import java.io.*;

import java.net.*;

import mylib.*;

public class EchoServer extends Server

{

public EchoServer( int port ) {

// The superclass knows what to do with the port number, we

// don't have to care about it

super( port );

}

// This is called by the Server class when a connection

// comes in. "in" and "out" come from the incoming socket

// connection

public void handleConnection( Socket socket ) {

try {

InputStream in = socket.getInputStream();

OutputStream out = socket.getOutputStream();

// just copy the input to the output

while (true)

out.write( in.read() );

} catch( IOException ie ) {

System.out.println( ie );

}

}

protected void cleanUp() {

System.out.println( "Cleaning up" );

}

static public void main( String args[] ) throws Exception {

// Grab the port number from the command-line

int port = Integer.parseInt( args[0] );

// Have debugging info sent to standard error stream

Server.setDebugStream( System.err );

// Create the server, and it's up and running

new EchoServer( port );

}

}

mylib.Server

// $Id$

package mylib;

import java.io.*;

import java.net.*;

abstract public class Server implements Runnable

{

// the port we'll be listening on

private int port;

// how many connections we've handled

int numConnections;

// the Reporter that's reporting on this Server

private Reporter reporter;

// set this to true to tell the thread to stop accepting

// connections

private boolean mustQuit = false;

public Server( int port ) {

// remember the port number so the thread can

// listen on it

this.port = port;

// the constructor starts a background thread

new Thread( this ).start();

// and start a reporter

reporter = new Reporter( this );

}

// this is our background thread

public void run() {

ServerSocket ss = null;

try {

// get ready to listen

ss = new ServerSocket( port );

while( !mustQuit ) {

// give out some debugging info

debug( "Listening on "+port );

// wait for an incoming connection

Socket s = ss.accept();

// record that we got another connection

numConnections++;

// more debugging info

debug( "Got connection on "+s );

// process the connection -- this is implemented

// by the subclass

handleConnection( s );

}

} catch( IOException ie ) {

debug( ie.toString() );

}

debug( "Shutting down "+ss );

cleanUp();

}

// the default implementation does nothing

abstract public void handleConnection( Socket s );

// tell the thread to stop accepting connections

public void close() {

mustQuit = true;

reporter.close();

}

// Put any last-minute clean-up stuff in here

protected void cleanUp() {

}

// everything below provides a simple debug system for

// this package

// set this to a print stream if you want debug info

// sent to it; otherwise, leave it null

static private PrintStream debugStream;

// we have two versions of this ...

static public void setDebugStream( PrintStream ps ) {

debugStream = ps;

}

// ... just for convenience

static public void setDebugStream( OutputStream out ) {

debugStream = new PrintStream( out );

}

// send debug info to the print stream, if there is one

static public void debug( String s ) {

if (debugStream != null)

debugStream.println( s );

}

}

mylib.Reporter

// $Id$

package mylib;

class Reporter implements Runnable

{

// the Server we are reporting on

private Server server;

// our background thread

private Thread thread;

// set this to true to tell the thread to stop accepting

// connections

private boolean mustQuit = false;

Reporter( Server server ) {

this.server = server;

// create a background thread

thread = new Thread( this );

thread.start();

}

public void run() {

while (!mustQuit) {

// do the reporting

Server.debug( "server has had "+server.numConnections+" connections" );

// then pause a while

try {

Thread.sleep( 5000 );

} catch( In

[1] [2] [3] [4] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有