任何一種面向?qū)ο笳Z(yǔ)言都有它的庫(kù)。任何一種面向?qū)ο蟮恼Z(yǔ)言也都離不開(kāi)庫(kù)的支持。用我們熟悉的 面向?qū)ο笳Z(yǔ)言為例子,C++有STL,Java有API函數(shù),具體到開(kāi)發(fā)工具,Visual C++提供了MFC, Borland C++提供了OWL。也有很多第三方提供的庫(kù)。我們?cè)陂_(kāi)發(fā)應(yīng)用程序的時(shí)候,也發(fā)覺(jué)我們也 許需要某些特定的庫(kù)來(lái)完成特定的功能。那么,如何編寫(xiě)自己的庫(kù)呢?
利用Java的面向?qū)ο筇匦?,如封裝,繼承,和一些設(shè)計(jì)模式,我們可以用標(biāo)準(zhǔn)的方法來(lái)建立自己的 庫(kù)。需要明白的一點(diǎn):在你需要完成某個(gè)功能的時(shí)候,不要用專有的、特定的方法去編寫(xiě)代碼,而 要全盤(pán)考慮,用通用的方法來(lái)完成,這樣,在積累了一定數(shù)量的庫(kù)以后,你就能重用這些庫(kù)來(lái)完成 新的功能,而不用每回都重頭編寫(xiě)代碼。這也是面向?qū)ο笳Z(yǔ)言提供給我們的好處。也可以用J2EE的 規(guī)范為例子,J2EE提供了一個(gè)CBT(Component Based Transaction),所有的組件都尊崇J2EE規(guī)范,在 CBT中運(yùn)行,這樣,編寫(xiě)開(kāi)發(fā)并且重用標(biāo)準(zhǔn)的通用的組件庫(kù),可以縮短開(kāi)發(fā)周期節(jié)約成本,并且可 以在任何符合J2EE規(guī)范的應(yīng)用程序服務(wù)器(APPLICATION SERVER)中運(yùn)行,并且可以繼承,擴(kuò)展已 有的組件庫(kù)完成新的任務(wù)或者適應(yīng)新的變化。 在本文中,我將先討論如何建立自己的庫(kù),需要根據(jù)哪些標(biāo)準(zhǔn),然后給出一個(gè)簡(jiǎn)單的例子。在第二 部分中,我將通過(guò)一個(gè)功能比較完善的庫(kù)來(lái)做進(jìn)一步的討論。 什么是庫(kù)?庫(kù)是一個(gè)可以重用的組件,它采用通用的設(shè)計(jì),完成通用的任務(wù),可以節(jié)約開(kāi)發(fā)者的時(shí) 間,縮短開(kāi)發(fā)周期節(jié)約開(kāi)發(fā)成本。一個(gè)設(shè)計(jì)完善的庫(kù),并不只是為了完成某一個(gè)特定的任務(wù),而是 可以完成各種不同的任務(wù)。設(shè)計(jì)一個(gè)庫(kù)是困難的。寫(xiě)一個(gè)算法并不難,但是設(shè)計(jì)庫(kù)的時(shí)候需要一種 比較好的結(jié)構(gòu),它能夠被用在各種需要的環(huán)境下,完成各種不同的任務(wù),但是還不能影響使用它的 程序代碼結(jié)構(gòu)。 為什么要重用代碼?重頭開(kāi)發(fā)一個(gè)新的軟件,工作量是非常巨大的,不論你用什么工具什么語(yǔ)言。 而代碼重用能夠節(jié)約大部分時(shí)間,而把時(shí)間花在新的功能的開(kāi)發(fā)上。從一定的意義上來(lái)說(shuō),寫(xiě)一個(gè) 新的軟件是利用了現(xiàn)有的代碼,重新拼裝以實(shí)現(xiàn)新的功能。從另外一個(gè)角度上來(lái)講,即使你沒(méi)有打 算把你寫(xiě)的代碼變成一個(gè)通用的庫(kù)并分發(fā)給其他人使用,從設(shè)計(jì)的角度來(lái)講,采用一種全盤(pán)的通用 的設(shè)計(jì)方法也能讓你對(duì)所要完成的任務(wù)有更好的理解,并且優(yōu)化你的設(shè)計(jì)過(guò)程,從而優(yōu)化你的代碼 結(jié)構(gòu)。 采用開(kāi)發(fā)庫(kù)并且讓別人來(lái)使用它的方式,能夠幫助你在使用它的時(shí)候發(fā)現(xiàn)它的設(shè)計(jì)上的缺陷或者代 碼中的錯(cuò)誤,并幫助你改正它。比方說(shuō),你寫(xiě)了一個(gè)庫(kù)讓別人來(lái)使用,你不得不考慮通用的設(shè)計(jì), 因?yàn)槟悴⒉荒茴A(yù)見(jiàn)別人將在什么環(huán)境下使用和使用的目的。在其他人使用你的庫(kù)的過(guò)程中,可能會(huì) 遇到一些問(wèn)題,有的可能是你的文檔寫(xiě)得不夠清楚明白,有的也可能是你程序上的錯(cuò)誤,也有可能 是使用者覺(jué)得在結(jié)構(gòu)上使用起來(lái)不方便或者不正確。那么你可以繼續(xù)作一些修改工作,在保持結(jié)構(gòu) 和接口不變化的情況下,做一些調(diào)整。 在設(shè)計(jì)庫(kù)的時(shí)候,你需要以一個(gè)使用者的眼光來(lái)看問(wèn)題,考慮如何設(shè)計(jì)和實(shí)現(xiàn)它。你需要明白, 1、需要解決的問(wèn)題是什么?需要達(dá)到一個(gè)什么目的? 2、使用者關(guān)心的問(wèn)題是什么?使用者需要得到一個(gè)什么結(jié)果? 3、使用者不需要關(guān)心的問(wèn)題是什么?什么細(xì)節(jié)是可以對(duì)使用者隱藏的? 下面,我們用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明如何設(shè)計(jì)和實(shí)現(xiàn)一個(gè)有用處的庫(kù)。 設(shè)計(jì)一個(gè)網(wǎng)絡(luò)服務(wù)程序,我們需要考慮幾點(diǎn): 1、監(jiān)聽(tīng)一個(gè)端口 2、接受連接 3、讀取或者寫(xiě)入連接的流 4、處理輸入的數(shù)據(jù),并且返回一個(gè)結(jié)果 對(duì)于我們將要實(shí)現(xiàn)的庫(kù)來(lái)說(shuō),需要完成的是前三點(diǎn),而最后一點(diǎn)我們留給使用者去實(shí)現(xiàn),這也是使 用者需要完成和關(guān)心的地方。 庫(kù)的主要類叫做Server, 測(cè)試的類叫做EchoServer. EchoServer實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的服務(wù),從客戶端讀 取數(shù)據(jù),并且返回同樣的數(shù)據(jù)。 設(shè)計(jì)原則一:封裝 一個(gè)好的庫(kù)必須是一個(gè)緊湊的關(guān)系緊密的整體,而不是一個(gè)分散的關(guān)系松散的對(duì)象的集合。 package是Java提供的一種類庫(kù)的封裝機(jī)制。一個(gè)package是一個(gè)Java類文件的集合,存放在同一個(gè)目 錄中。package有專有的名字空間。 專有的名字空間的一個(gè)好處是,你不用擔(dān)心名稱的沖突。因?yàn)椋绻愕念惖拿Q和別人的類的名 稱沖突,但是他們不在同一個(gè)package中,利用這一點(diǎn)可以避免名字的沖突。 每一個(gè)package都有一個(gè)字符串來(lái)代表,比如java.lang, 或者javax.swing.plaf.basic.實(shí)際上每一個(gè)類的 全名都是由package的名字加上類的名字來(lái)代表的,這樣就避免了名字的沖突,比 如,java.lang.Object或者javax.swing.plaf.basic.BasicMenuBarUI. 注意,有一個(gè)特殊的package叫做default package。如果你不聲明你的類屬于任何一個(gè)package,那么 它就被假定屬于default package. 每一個(gè)package的名字都對(duì)應(yīng)一個(gè)目錄。比如,java.lang.Object 存放在java/lang/Object.java中,每一 個(gè).對(duì)應(yīng)一個(gè)/. default package存放的目錄是當(dāng)前目錄。 聲明一個(gè)package. // Server.java package mylib; public class Server implements Runnable { // ... 如果有import語(yǔ)句,必須放在package語(yǔ)句的后面。 當(dāng)然你也可以引入別的package. 例如: import mylib.Server; // ... Server server = new Server( portNum ); Java允許你決定package中的哪些類對(duì)外部是可見(jiàn)的。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 { 設(shè)計(jì)原則二:繼承 在我們的例子中,Server是主要的類。如果你看這個(gè)類的代碼,就能看到,它本身其實(shí)什么也不 做。主循環(huán)用來(lái)監(jiān)聽(tīng)連接。當(dāng)連接建立以后,它把處理連接的任務(wù)交給一個(gè)叫做handleConnection() 的函數(shù)。 // subclass must supply an implementation abstract public void handleConnection( Socket s ); 因?yàn)闆](méi)有實(shí)現(xiàn)這一函數(shù),所以這個(gè)類被聲明為abstract,使用者必須實(shí)現(xiàn)這個(gè)函數(shù)。 // 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 ); } } 可以說(shuō),這一繼承的過(guò)程叫做定制。因?yàn)樵赟erver類中,并沒(méi)有定義該函數(shù)的動(dòng)作,而是把這個(gè)定 義的過(guò)程留給使用者,讓他們來(lái)完成所需要的特定的功能。 另外一個(gè)定制函數(shù):cleanUp(). 在設(shè)計(jì)類的時(shí)候,往往你能考慮到使用者需要的功能,例如上面的handleConnection().但是,也需要 考慮另外一種定制,例如在這里,在Server退出后臺(tái)運(yùn)行方式的時(shí)候,調(diào)用了這個(gè)cleanUp()函數(shù), 在Server類中的實(shí)現(xiàn)為空,什么都不做,這把機(jī)會(huì)留給使用者,使用者可以用這個(gè)函數(shù)來(lái)做一些清 除工作,這種函數(shù)也可以稱之為"鉤子"。 設(shè)計(jì)原則三:調(diào)試 沒(méi)有人能夠做到寫(xiě)出一個(gè)絕對(duì)完美的程序,沒(méi)有任何的錯(cuò)誤。所以,調(diào)試是不可缺少的。有時(shí)候, 使用者可能會(huì)遇到一個(gè)問(wèn)題,從而需要知道在庫(kù)的代碼中發(fā)生了什么問(wèn)題。這個(gè)錯(cuò)誤可能是庫(kù)代碼 的問(wèn)題,也可能是使用者的代碼在庫(kù)代碼中引起的問(wèn)題。 如果你提供了庫(kù)的源代碼,使用者可以用debugger來(lái)調(diào)試錯(cuò)誤。但是,你不能完全依賴于調(diào)試器。 在庫(kù)代碼中加入打印調(diào)試信息的語(yǔ)句,是一個(gè)好習(xí)慣。它可以幫助使用者明白,什么地方發(fā)生了錯(cuò) 誤。 下面的例子說(shuō)明了這一技術(shù)。使用者的代碼使用Server.setDebugStream(),指定一個(gè)PrintStream對(duì) 象。然后,調(diào)試信息就被輸出到這個(gè)流中。 // 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; } 當(dāng)使用者使用了調(diào)試的流的時(shí)候,你的庫(kù)代碼可以打印錯(cuò)誤: // send debug info to the print stream, if there is one static public void debug( String s ) { if (debugStream != null) debugStream.println( s ); } 下面,來(lái)完整的看一看這個(gè)具體的例子: 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( InterruptedException ie ) {} } } // tell the background thread to quit public void close() { mustQuit = true; } } 下面,我們以一個(gè)具體的有實(shí)際用處的類庫(kù)來(lái)進(jìn)一步討論庫(kù)的設(shè)計(jì)方法。這個(gè)庫(kù)是jregex. (jregex.sourceforge.net)。這是一個(gè)用Java實(shí)現(xiàn)的兼容Perl 5.6的正則表達(dá)式的庫(kù)。有很多這樣的庫(kù), 比如gnu.regexp,com.stevesoft.pat, 還有J2SE SDK 1.4中新增加的regex.為什么選用jregex呢?是因?yàn)?它是目前源代碼公開(kāi)的regex庫(kù)中兼容Perl 5.6的正則表達(dá)式,而且剛剛更新過(guò)源代碼,并且是穩(wěn)定 版,另外一個(gè)就是,它的內(nèi)核算法選用了NFA(Not Finite Automata)。 要了解一個(gè)包,在看源代碼之前先應(yīng)該看的就是它的API文檔。那么,看文檔的第一步應(yīng)該看什么 呢?當(dāng)然是樹(shù)形結(jié)構(gòu)。 Class Hierarchy class java.lang.Object class jregex.Matcher (implements jregex.MatchResult) class jregex.Optimizer class jregex.util.io.PathPattern class jregex.Pattern (implements jregex.REFlags, java.io.Serializable) class jregex.PerlSubstitution (implements jregex.Substitution) class jregex.Replacer class jregex.RETokenizer (implements java.util.Enumeration) class java.lang.Throwable (implements java.io.Serializable) class java.lang.Exception class java.lang.RuntimeException class java.lang.IllegalArgumentException class jregex.PatternSyntaxException class jregex.util.io.WildcardFilter (implements java.io.FilenameFilter) Interface Hierarchy interface jregex.MatchIterator interface jregex.MatchResult interface jregex.REFlags interface jregex.Substitution interface jregex.TextBuffer interface jregex.Replacer.WriterWrap -------------------------------------------------------------------------------- 在一個(gè)正則表達(dá)式中,我們知道有兩個(gè)元素很重要,第一個(gè)就是Pattern(模式), 第二個(gè)是Matcher(匹 配結(jié)果字符串)。在jregex中,Pattern類實(shí)現(xiàn)了jregex.REFlags interface, 和java.io.Serializable。先來(lái)看 看jregex.REFlags的說(shuō)明。jregex.REFlags定義了一些靜態(tài)的常量,看起來(lái)是一些標(biāo)志。Pattern實(shí)現(xiàn)了 jregex.REFlags, 也就是說(shuō),Pattern類中包含了這些靜態(tài)的常量。 下一步,我們看看Pattern的API說(shuō)明: Pattern是一個(gè)預(yù)編譯好的正則表達(dá)式的表示。要匹配一個(gè)正則表達(dá)式,先創(chuàng)建一個(gè)Pattern實(shí)例: Pattern p=new Pattern(myExpr); 然后取得Matcher的實(shí)例 Matcher matcher=p.matcher(myText); Matcher的實(shí)例是一個(gè)自動(dòng)的匹配和搜索的對(duì)象。它提供如下方法: 搜索匹配結(jié)果: matcher.find() or matcher.findAll(); 監(jiān)測(cè)是否全文匹配 : matcher.matches(); 監(jiān)測(cè)是否匹配開(kāi)頭 : matcher.isStart(); 帶選項(xiàng)的查找 : matcher.find(int options) 標(biāo)志 標(biāo)志(參考REFlags)改變了在預(yù)編譯的時(shí)候正則表達(dá)式符號(hào)的意義。這些標(biāo)志是: REFlags.IGNORE_CASE - 忽略大小寫(xiě) REFlags.MULTILINE - 用^和$來(lái)表示一行文本的開(kāi)頭和結(jié)尾 REFlags.DOTALL - 用.來(lái)表示回車(chē)換行 REFlags.IGNORE_SPACES - 忽略空格 REFlags.UNICODE - 使用UNICODE, 即w, d不再被解釋為正則表達(dá)式的意義,而是被解釋為 UNICODE. REFlags.XML_SCHEMA - 使用XML語(yǔ)義。 線程 Pattern是線程安全的。也就是說(shuō),你可以在不同的線程中使用同一個(gè)Pattern的實(shí)例。 在API函數(shù)說(shuō)明中,我們還能看到Pattern類的public方法。這一點(diǎn)將在下面有用處。先來(lái)看看: 構(gòu)造函數(shù) Pattern(java.lang.String regex) Compiles an expression with default flags. Pattern(java.lang.String regex, int flags) Compiles a regular expression using REFlags. Pattern(java.lang.String regex, java.lang.String flags) Compiles a regular expression using Perl5-style flags. 方法 int groupCount() How many capturing groups this expression includes? java.lang.Integer groupId(java.lang.String name) Get numeric id for a group name. Matcher matcher() Returns a targetless matcher. Matcher matcher(char[] data, int start, int end) Returns a matcher for a specified region. Matcher matcher(MatchResult res, int groupId) Returns a matcher for a match result (in a performance-friendly way). Matcher matcher(MatchResult res, java.lang.String groupName) Just as above, yet with symbolic group name. Matcher matcher(java.io.Reader text, int length) Returns a matcher taking a text stream as target. Matcher matcher(java.lang.String s) Returns a matcher for a specified string. Replacer replacer(java.lang.String expr) Returns a replacer of a pattern by specified perl-like expression. Replacer replacer(Substitution model) Returns a replacer will substitute all occurences of a pattern through applying a user-defined substitution model. RETokenizer tokenizer(char[] data, int off, int len) Tokenizes a specified region by an occurences of the pattern. RETokenizer tokenizer(java.io.Reader in, int length) Tokenizes a specified region by an occurences of the pattern. RETokenizer tokenizer(java.lang.String text) Tokenizes a text by an occurences of the pattern. java.lang.String toString_d() Returns a less or more readable representation of a bytecode for the pattern. java.lang.String toString() 接下來(lái),我們來(lái)看看Pattern類的內(nèi)容。這里有兩種方法,一種是直接閱讀源代碼,另外一種是先用 工具分析一下Pattern類的內(nèi)容。這里,我采用第二種方法,用javap來(lái)看類的內(nèi)容。 [games]$javap -classpath .. -private jregex.Pattern Compiled from jregex/Pattern.java public class jregex.Pattern extends java.lang.Object implements java.io.Serializable, jregex.REFlags { java.lang.String stringRepr; jregex.Term root; jregex.Term root0; int memregs; int counters; int lookaheads; java.util.Hashtable namedGroupMap; private jregex.Pattern() throws jregex.PatternSyntaxException; public jregex.Pattern(java.lang.String) throws jregex.PatternSyntaxException; public jregex.Pattern(java.lang.String,java.lang.String) throws jregex.PatternSyntaxException; public jregex.Pattern(java.lang.String,int) throws jregex.PatternSyntaxException; private void compile(java.lang.String, int) throws jregex.PatternSyntaxException; public int groupCount(); public java.lang.Integer groupId(java.lang.String); public jregex.Matcher matcher(); public jregex.Matcher matcher(java.lang.String); public jregex.Matcher matcher(char[], int, int); public jregex.Matcher matcher(jregex.MatchResult, int); public jregex.Matcher matcher(jregex.MatchResult, java.lang.String); public jregex.Matcher matcher(java.io.Reader, int) throws java.io.IOException; public jregex.Replacer replacer(java.lang.String); public jregex.Replacer replacer(jregex.Substitution); public jregex.RETokenizer tokenizer(java.lang.String); public jregex.RETokenizer tokenizer(char[], int, int); public jregex.RETokenizer tokenizer(java.io.Reader, int) throws java.io.IOException; public java.lang.String toString(); public java.lang.String toString_d(); static int parseFlags(java.lang.String) throws jregex.PatternSyntaxException; static int parseFlags(char[], int, int) throws jregex.PatternSyntaxException; private static int getFlag(char) throws jregex.PatternSyntaxException; } 其中,要關(guān)心private和protected成員,因?yàn)樵谑褂妙惖臅r(shí)候,我們只要關(guān)心public成員就行了,但 是,要閱讀源代碼,明白類的構(gòu)成,就必須注意private和protected成員。 private Pattern() throws PatternSyntaxException{} public Pattern(String regex) throws PatternSyntaxException{ this(regex,DEFAULT); } public Pattern(String regex,String flags) throws PatternSyntaxException{ stringRepr=regex; compile(regex,parseFlags(flags)); } public Pattern(String regex, int flags) throws PatternSyntaxException{ stringRepr=regex; compile(regex,flags); } 可以看出,構(gòu)造函數(shù)中,有一個(gè)缺省的構(gòu)造函數(shù)是private。而第二個(gè)調(diào)用了最后一個(gè)構(gòu)造函數(shù),用 this()。第三個(gè)和最后一個(gè)都是用了一個(gè)函數(shù)compile來(lái)完成構(gòu)造正則表達(dá)式的任務(wù)。在上面javap的 輸出我們也可以看到,compile是一個(gè)private函數(shù)。 來(lái)看看它的說(shuō)明: private void compile(String regex,int flags) throws PatternSyntaxException{ Term.makeTree(regex,flags,this); } 具體到Term類,超出了本文的范圍,它采用了hashtable等方法,構(gòu)造了一個(gè)正則表達(dá)式,并返回。 而我們關(guān)心的是庫(kù)的結(jié)構(gòu)。從這里我們可以明白一點(diǎn):構(gòu)造函數(shù)往往需要調(diào)用另外一個(gè)構(gòu)造函數(shù)來(lái) 完成,而不需要把同樣的代碼在各個(gè)構(gòu)造函數(shù)中都實(shí)現(xiàn)。同時(shí),也可以采用另外一個(gè)private函數(shù)來(lái) 完成構(gòu)造函數(shù)的功能,而只要在構(gòu)造函數(shù)中調(diào)用它就行了。 從Matcher API說(shuō)明文檔中,我們可以看到,有兩種通過(guò)Pattern實(shí)例構(gòu)造Matcher實(shí)例的方法,而在 javap的輸出中可以看到,有幾種不同的matcher函數(shù)。 public Matcher matcher(){ return new Matcher(this); } public Matcher matcher(String s){ Matcher m=new Matcher(this); m.setTarget(s); return m; } public Matcher matcher(char[] data,int start,int end){ Matcher m=new Matcher(this); m.setTarget(data,start,end); return m; } public Matcher matcher(MatchResult res,int groupId){ Matcher m=new Matcher(this); if(res instanceof Matcher){ m.setTarget((Matcher)res,groupId); } else{ m.setTarget(res.targetChars(),res.start(groupId)+res.targetStart(),res.length(groupId)); } return m; } public Matcher matcher(Reader text,int length)throws IOException{ Matcher m=new Matcher(this); m.setTarget(text,length); return m; } 以上都是用來(lái)實(shí)現(xiàn)第一種返回matcher的方法??梢钥吹?,這幾種實(shí)現(xiàn)都是通過(guò)new Matcher實(shí)例, 以this(即Pattern實(shí)例)為參數(shù),然后根據(jù)參數(shù)不同調(diào)用Matcher.setTarget()方法。 public Matcher matcher(MatchResult res,String groupName){ Integer id=res.pattern().groupId(groupName); if(id==null) throw new IllegalArgumentException("group not found:"+groupName); int group=id.intValue(); return matcher(res,group); } 這是第二種返回matcher的方法。 從這里我們可以發(fā)現(xiàn)一種比較好的方法:當(dāng)你需要兩個(gè)互相關(guān)聯(lián)的類,一個(gè)類的實(shí)例需要構(gòu)造另一 個(gè)類的實(shí)例,可以在第一個(gè)類中用一個(gè)public方法,方法實(shí)現(xiàn)為調(diào)用第二個(gè)類的構(gòu)造函數(shù),并可以 用構(gòu)造出來(lái)的實(shí)例調(diào)用其他方法,根據(jù)第一個(gè)類的實(shí)例的數(shù)據(jù)來(lái)設(shè)置第二個(gè)類的實(shí)例。 同樣的,也有Replacer, 和Tokenizer的構(gòu)造方法。 public Replacer replacer(String expr){ return new Replacer(this,expr); } /** * Returns a replacer will substitute all occurences of a pattern * through applying a user-defined substitution model. * @param model a Substitution object which is in charge for match substitution * @see Replacer */ public Replacer replacer(Substitution model){ return new Replacer(this,model); } public RETokenizer tokenizer(String text){ return new RETokenizer(this,text); } /** * Tokenizes a specified region by an occurences of the pattern. * Note that a series of adjacent matches are regarded as a single separator. * The same as new RETokenizer(Pattern,char[],int,int); * @see RETokenizer * @see RETokenizer#RETokenizer(jregex.Pattern,char[],int,int) */ public RETokenizer tokenizer(char[] data,int off,int len){ return new RETokenizer(this,data,off,len); } /** * Tokenizes a specified region by an occurences of the pattern. * Note that a series of adjacent matches are regarded as a single separator. * The same as new RETokenizer(Pattern,Reader,int); * @see RETokenizer * @see RETokenizer#RETokenizer(jregex.Pattern,java.io.Reader,int) */ public RETokenizer tokenizer(Reader in,int length) throws IOException{ return new RETokenizer(this,in,length); } 回憶一下,我在本文的開(kāi)頭,曾經(jīng)提到過(guò):"一個(gè)好的庫(kù)必須是一個(gè)緊湊的關(guān)系緊密的整體,而不 是一個(gè)分散的關(guān)系松散的對(duì)象的集合。"從API說(shuō)明文檔所顯示的這個(gè)庫(kù)的樹(shù)形結(jié)構(gòu),并不能看出這 些類之間的聯(lián)系。而從源代碼的角度,我們則可以清楚地看到這一點(diǎn)。在這一部分的討論中,我們 也明白了兩點(diǎn): 1、如何編寫(xiě)重載構(gòu)造函數(shù) 2、在一個(gè)類的實(shí)例中返回另外一個(gè)類的實(shí)例 接下來(lái),看看Matcher類。這個(gè)類實(shí)現(xiàn)了MatchResult interface. 看看MatchResult的定義: [games]$javap -classpath .. -s jregex.MatchResult Compiled from jregex/MatchResult.java public interface jregex.MatchResult /* ACC_SUPER bit NOT set */ { public static final int MATCH; /* I */ public static final int PREFIX; /* I */ public static final int SUFFIX; /* I */ public static final int TARGET; /* I */ public abstract jregex.Pattern pattern(); /* ()Ljregex/Pattern; */ public abstract int groupCount(); /* ()I */ public abstract boolean isCaptured(); /* ()Z */ public abstract boolean isCaptured(int); /* (I)Z */ public abstract boolean isCaptured(java.lang.String); /* (Ljava/lang/String;)Z */ public abstract java.lang.String group(int); /* (I)Ljava/lang/String; */ public abstract boolean getGroup(int, java.lang.StringBuffer); /* (ILjava/lang/StringBuffer;)Z */ public abstract boolean getGroup(int, jregex.TextBuffer); /* (ILjregex/TextBuffer;)Z */ public abstract java.lang.String group(java.lang.String); /* (Ljava/lang/String;)Ljava/lang/String; */ public abstract boolean getGroup(java.lang.String, java.lang.StringBuffer); /* (Ljava/lang/String;Ljava/lang/StringBuffer;)Z */ public abstract boolean getGroup(java.lang.String, jregex.TextBuffer); /* (Ljava/lang/String;Ljregex/TextBuffer;)Z */ public abstract java.lang.String prefix(); /* ()Ljava/lang/String; */ public abstract java.lang.String suffix(); /* ()Ljava/lang/String; */ public abstract java.lang.String target(); /* ()Ljava/lang/String; */ public abstract int targetStart(); /* ()I */ public abstract int targetEnd(); /* ()I */ public abstract char targetChars()[]; /* ()[C */ public abstract int start(); /* ()I */ public abstract int end(); /* ()I */ public abstract int length(); /* ()I */ public abstract int start(int); /* (I)I */ public abstract int end(int); /* (I)I */ public abstract int length(int); /* (I)I */ public abstract char charAt(int); /* (I)C */ public abstract char charAt(int, int); /* (II)C */ } jregex.MatchResult定義了一些abstract函數(shù)。有什么作用?在后面我們將會(huì)討論到。 再看看Matcher的實(shí)現(xiàn)。 [games]$javap -classpath .. -s jregex.Matcher Compiled from jregex/Matcher.java public class jregex.Matcher extends java.lang.Object implements jregex.MatchResult { public static final int ANCHOR_START; /* I */ public static final int ANCHOR_LASTMATCH; /* I */ public static final int ANCHOR_END; /* I */ public static final int ACCEPT_INCOMPLETE; /* I */ jregex.Matcher(jregex.Pattern); /* (Ljregex/Pattern;)V */ public final void setTarget(jregex.Matcher, int); /* (Ljregex/Matcher;I)V */ public void setTarget(java.lang.String); /* (Ljava/lang/String;)V */ public void setTarget(java.lang.String, int, int); /* (Ljava/lang/String;II)V */ public void setTarget(char[], int, int); /* ([CII)V */ public final void setTarget(char[], int, int, boolean); /* ([CIIZ)V */ public void setTarget(java.io.Reader, int) throws java.io.IOException; /* (Ljava/io/Reader;I)V */ public final boolean isStart(); /* ()Z */ public final boolean matches(); /* ()Z */ public final boolean matches(java.lang.String); /* (Ljava/lang/String;)Z */ public void setPosition(int); /* (I)V */ public final boolean find(); /* ()Z */ public final boolean find(int); /* (I)Z */ public jregex.MatchIterator findAll(); /* ()Ljregex/MatchIterator; */ public jregex.MatchIterator findAll(int); /* (I)Ljregex/MatchIterator; */ public final boolean proceed(); /* ()Z */ public final boolean proceed(int); /* (I)Z */ public final void skip(); /* ()V */ public java.lang.String toString(); /* ()Ljava/lang/String; */ public jregex.Pattern pattern(); /* ()Ljregex/Pattern; */ public java.lang.String target(); /* ()Ljava/lang/String; */ public char targetChars()[]; /* ()[C */ public int targetStart(); /* ()I */ public int targetEnd(); /* ()I */ public char charAt(int); /* (I)C */ public char charAt(int, int); /* (II)C */ public final int length(); /* ()I */ public final int start(); /* ()I */ public final int end(); /* ()I */ public java.lang.String prefix(); /* ()Ljava/lang/String; */ public java.lang.String suffix(); /* ()Ljava/lang/String; */ public int groupCount(); /* ()I */ public java.lang.String group(int); /* (I)Ljava/lang/String; */ public java.lang.String group(java.lang.String); /* (Ljava/lang/String;)Ljava/lang/String; */ public boolean getGroup(int, jregex.TextBuffer); /* (ILjregex/TextBuffer;)Z */ public boolean getGroup(java.lang.String, jregex.TextBuffer); /* (Ljava/lang/String;Ljregex/TextBuffer;)Z */ public boolean getGroup(int, java.lang.StringBuffer); /* (ILjava/lang/StringBuffer;)Z */ public boolean getGroup(java.lang.String, java.lang.StringBuffer); /* (Ljava/lang/String;Ljava/lang/StringBuffer;)Z */ public java.lang.String groups()[]; /* ()[Ljava/lang/String; */ public java.util.Vector groupv(); /* ()Ljava/util/Vector; */ public final boolean isCaptured(); /* ()Z */ public final boolean isCaptured(int); /* (I)Z */ public final boolean isCaptured(java.lang.String); /* (Ljava/lang/String;)Z */ public final int length(int); /* (I)I */ public final int start(int); /* (I)I */ public final int end(int); /* (I)I */ public java.lang.String toString_d(); /* ()Ljava/lang/String; */ static {}; /* ()V */ 先來(lái)看看它的構(gòu)造函數(shù),這個(gè)函數(shù)在Pattern中被調(diào)用用來(lái)構(gòu)造Matcher類的實(shí)例。 Matcher(Pattern regex){ //注意下面這一行,它說(shuō)明Matcher類的內(nèi)部有一個(gè)指向Pattern實(shí)例的reference. this.re=regex; //int memregCount=(memregs=new MemReg[regex.memregs]).length; //for(int i=0;i<memregCount;i++){ // this.memregs[i]=new MemReg(-1); //unlikely to SearchEntry, in this case we know memreg indicies by d efinition //} //counters=new int[regex.counters]; //int lookaheadCount=(lookaheads=new LAEntry[regex.lookaheads]).length; //for(int i=0;i<lookaheadCount;i++){ // this.lookaheads[i]=new LAEntry(); //} //定義了一些內(nèi)部的數(shù)據(jù),MemReg是一個(gè)有三個(gè)整數(shù)的類。類的聲明見(jiàn)下。 int memregCount,counterCount,lookaheadCount; if((memregCount=regex.memregs)>0){ MemReg[] memregs=new MemReg[memregCount]; for(int i=0;i<memregCount;i++){ memregs[i]=new MemReg(-1); //unlikely to SearchEntry, in this case we know memreg indicies by defin ition } this.memregs=memregs; } if((counterCount=regex.counters)>0) counters=new int[counterCount]; //定義了一些內(nèi)部的數(shù)據(jù)。類的聲明見(jiàn)下。 if((lookaheadCount=regex.lookaheads)>0){ LAEntry[] lookaheads=new LAEntry[lookaheadCount]; for(int i=0;i<lookaheadCount;i++){ lookaheads[i]=new LAEntry(); } this.lookaheads=lookaheads; } this.memregCount=memregCount; this.counterCount=counterCount; this.lookaheadCount=lookaheadCount; first=new SearchEntry(memregCount,counterCount); defaultEntry=new SearchEntry(memregCount,counterCount); minQueueLength=regex.stringRepr.length()/2; // evaluation!!! } 把這兩個(gè)類說(shuō)明為default屬性,說(shuō)明這兩個(gè)類在jregex這個(gè)包的內(nèi)部可見(jiàn),而在外部是不可見(jiàn)的。這 兩個(gè)類的作用是專有的,而不是通用的。回憶一下本文前面提到過(guò)的,包的封裝,"如果你不想讓 類被外部的代碼使用,可以用缺省的屬性,去掉public." class MemReg{ int index; int in=-1,out=-1; int tmp=-1; //for assuming at GROUP_IN MemReg(int index){ this.index=index; } void reset(){ in=out=-1; } } class LAEntry{ int index; SearchEntry top,actual; } 另外,關(guān)于包的結(jié)構(gòu)和包中的類的關(guān)系,我們感興趣的還有,MatchIterator。 [games]$javap -classpath .. -s jregex.MatchIterator Compiled from jregex/MatchIterator.java public interface jregex.MatchIterator /* ACC_SUPER bit NOT set */ { public abstract boolean hasMore(); /* ()Z */ public abstract jregex.MatchResult nextMatch(); /* ()Ljregex/MatchResult; */ public abstract int count(); /* ()I */ } 這是一個(gè)interface, 定義了iterator的常用方法,列出所有的MatchResult的實(shí)例。 從這里可以看出,定義一個(gè)MatchResult interface然后再用Matcher來(lái)實(shí)現(xiàn)這個(gè)interface是一種比較好 的方法。因?yàn)?,這樣定義了比較好的層次結(jié)構(gòu),對(duì)于后面的擴(kuò)展,程序的更新發(fā)展都有比較大的好 處。在這里也可以看出,MatchIterator的方法返回的也是MatchResult interface, 而不是Matcher類。 同時(shí)可以看到MatchIterator本身也是一個(gè)interface。這就體現(xiàn)了另外一個(gè)我們需要明白的重要問(wèn)題: 對(duì)interface編程,而不是對(duì)類編程。具體說(shuō)來(lái),如果在以后重新寫(xiě)了一個(gè)新的類,叫做 AnotherMatcher, 同樣也是實(shí)現(xiàn)MatchResult interface, 在AnotherMatcher中同樣可以使用方法來(lái)返回一 個(gè)MatchIterator的實(shí)例,它也許是Matcher類的實(shí)例,也可以是AnotherMatcher類的實(shí)例。但是,雖 然我們作了很多改動(dòng),但是沒(méi)有影響到以前的程序,不需要對(duì)以前的程序作任何的改動(dòng)。從使用者 的角度來(lái)看,如果他只是使用了MatchResult和MatchIterator這兩個(gè)interfaces中定義的方法,那么, 不論是用Matcher還是AnotherMatcher,他都不需要修改他的代碼。 下一個(gè)需要看的函數(shù)是findAll(). //調(diào)用了帶參數(shù)的重載函數(shù)findAll(int) public MatchIterator findAll(){ return findAll(0); } /** * Returns an iterator over the matches found by subsequently calling find(options), the search starts from t he zero position. */ public MatchIterator findAll(final int options){ //setPosition(0); //不用關(guān)心具體的實(shí)現(xiàn)方法,但是我們可以看到,在這里它定義了一個(gè)anonymous inner class, 這 個(gè)inner class實(shí)現(xiàn)了MatchIterator,用于把Matcher中匹配到的結(jié)果一個(gè)個(gè)的返回。這個(gè)inner class的 對(duì)象實(shí)例的reference被findAll()方法返回。能夠用于外部的一個(gè)reference,并能讀出這些結(jié)果。這也 是一種需要注意的方法。 return new MatchIterator(){ private boolean checked=false; private boolean hasMore=false; public boolean hasMore(){ if(!checked) check(); return hasMore; } public MatchResult nextMatch(){ if(!checked) check(); if(!hasMore) throw new NoSuchElementException(); checked=false; return Matcher.this; } private final void check(){ hasMore=find(options); checked=true; } public int count(){ if(!checked) check(); if(!hasMore) return 0; int c=1; while(find(options))c++; checked=false; return c; } }; } 在上面這段代碼中,我們明白了如下3點(diǎn): 1、對(duì)interface編程,而不是對(duì)類編程。 2、如何編寫(xiě)一個(gè)iterator(迭代器)。需要有起碼的兩個(gè)函數(shù):hasMore(), NextElement()。這樣,使用 者可以用如下循環(huán)讀取迭代器中的內(nèi)容: for(;iterator.hasMore();) iterator.NextElement(); 3、把處理方法和處理結(jié)果分開(kāi)。用Matcher來(lái)表示匹配的方法的具體實(shí)現(xiàn),而把結(jié)果放在另外一個(gè) 類中。這樣做的好處是處理方法和結(jié)果分開(kāi),更加的靈活可以任意的更改處理的方法,而結(jié)果類不 受到影響。也可以更改結(jié)果類的讀取方式,但是不影響處理方法類的實(shí)現(xiàn)。這種方法已經(jīng)被J2SE SDK中的很多集合類所使用。也可以參考它們的實(shí)現(xiàn)方法。 當(dāng)然,還有一點(diǎn)需要注意的就是使用了匿名的inner class方法。 jregex中可以討論的問(wèn)題還有很多,比如它所采用的Not Finite Automata方法,它所采用的正則表達(dá) 式規(guī)范等等。但是,作為本文所討論的范圍,即如何設(shè)計(jì)和實(shí)現(xiàn)一個(gè)庫(kù),已經(jīng)可以展示它的設(shè)計(jì)方 法了。有興趣的讀者可以到j(luò)regex.sourceforge.net下載它的源代碼包,二進(jìn)制文件包,使用范例和文 檔說(shuō)明做進(jìn)一步的研究。 小結(jié): 在本文中,我們討論了設(shè)計(jì)和實(shí)現(xiàn)一個(gè)Java庫(kù)的若干原則,并用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明了這些原 則。隨后,我們用一個(gè)實(shí)際應(yīng)用范圍很廣的庫(kù)來(lái)討論了其他的一些需要注意的問(wèn)題。 設(shè)計(jì)原則一:封裝 一個(gè)好的庫(kù)必須是一個(gè)緊湊的關(guān)系緊密的整體,而不是一個(gè)分散的關(guān)系松散的對(duì)象的集合。 設(shè)計(jì)原則二:繼承 采用abstract函數(shù),interface, 和"鉤子"函數(shù)。 設(shè)計(jì)原則三:調(diào)試 在庫(kù)代碼中加入打印調(diào)試信息的語(yǔ)句。 以及一些設(shè)計(jì)方法: 1、如何編寫(xiě)重載構(gòu)造函數(shù)。 2、在一個(gè)類的實(shí)例中返回另外一個(gè)類的實(shí)例 3、對(duì)interface編程,而不是對(duì)類編程。 4、如何編寫(xiě)一個(gè)iterator(迭代器)。 5、把處理方法和處理結(jié)果分開(kāi)。 6、如何使用inner class. 同時(shí),我們也討論了閱讀一個(gè)Java庫(kù)源代碼的方法: 1、閱讀API文檔說(shuō)明,庫(kù)的樹(shù)形結(jié)構(gòu),明白類,interface的關(guān)系。 2、用javap來(lái)列出類的函數(shù)說(shuō)明,變量,interface中定義的函數(shù),常量等等。 3、注意private, protected函數(shù)。 |
|