地址:ivannwan@gmail.com.
ivaneeo's blog自由的力量,自由的生活。 |
大家如果需要gmail,只要發郵件給我.
地址:ivannwan@gmail.com.
兩個classes都需要使用對方特性,但其間只有一條單向連接(one-way link)。
添加一個反向,并使修改函數(modifiers)能夠同時更新兩條連接。(注譯:這里的指針等同于句柄(handle),修改函數(modifier)指的是改變雙方關系者) ![]()
我有這樣一句sql語句:select ${ 0},${ 1},${2 },${2 } from t where ${0}='${2}'。
我的目的是先找出所有的變量,并把變量的值替換為: V變量名。 String statement = "select ${ 0},${ 1},${2 },${2 } from t where ${0}='${2}'"; System.out.println(statement); Matcher m = Pattern.compile("(\\$\\{\\s*(\\d+)\\s*\\})").matcher(statement); StringBuffer buffer = new StringBuffer(); while(m.find()) { System.out.println("Matched:'" + m.group(1) + "' at position " + m.start()); System.out.println("Matched:'" + m.group(2) + "' at position " + m.start()); int temp = Integer.parseInt(m.group(2)); if(temp == 0) m.appendReplacement(buffer, "V0"); else if(temp == 1) m.appendReplacement(buffer, "V1"); else if(temp == 2) m.appendReplacement(buffer, "V2"); } m.appendTail(buffer); System.out.println(buffer.toString()); 輸出結果: select ${ 0},${ 1},${2 },${2 } from t where ${0}='${2}' Matched:'${ 0}' at position 7 Matched:'0' at position 7 Matched:'${ 1}' at position 13 Matched:'1' at position 13 Matched:'${2 }' at position 19 Matched:'2' at position 19 Matched:'${2 }' at position 25 Matched:'2' at position 25 Matched:'${0}' at position 44 Matched:'0' at position 44 Matched:'${2}' at position 50 Matched:'2' at position 50 select V0,V1,V2,V2 from t where V0='V2' 這里要逐個替換就要使用類Matcher的appendReplacement()和appendTail()方法。
如果你愛編程,請你愛C語言;
如果你愛C語言,請你愛指針; 如果你愛指針,請你愛指針的指針! 計算機學的許多知識都具有相通性,因而,不斷追趕時髦技術而忽略基本功的做法是徒勞無意的。我們最多需要"精通"三種語言,最佳拍檔是匯編、C、C++(或JAVA),很顯然,如果你"精通"了這三種語言,其它語言你應該是可以很快"熟悉"的,否則你就沒有"精通"它們。 在性能優化方面永遠注意80-20準備,不要優化程序中開銷不大的那80%,這是勞而無功的。除了編程上的技巧外,為提高系統的運行效率,我們通常也需要最大可能地利用各種硬件設備自身的特點來減小其運轉開銷。
有的程序員這樣寫:
這個語法沒有確切表達代碼的含義,我們從for(;;)看不出什么,只有弄明白for(;;)在C語言中意味著無條件循環才明白其意。 下面是幾個"著名"的死循環: (1)操作系統是死循環; (2)WIN32程序是死循環; (3)嵌入式系統軟件是死循環; (4)多線程程序的線程處理函數是死循環。 下面列舉了網絡的消息種類,它包含有分布于每個消息地點( site) 分級 所處理的內容主題 comp 有關計算機的主題。其中有許多相當重要的討論 還有更多種類的消息分布沒有這么廣泛; 我們將在這個附錄的后面 計算機組 名稱 描述 comp.binaries.ibm.pc 適用于IBM PC/MS—DOS的僅二進制形式的郵寄(有改動) comp.binaries.os2 可在OS/2 ABI環境下使用的二進制文件(有改動) comp.dcom.telecom 無線通信的摘要(有改動) comp.os.msdos.programmer MS—DOS機器上的程序設計 comp.society.futures 影響未來計算機技術的大事 comp.sources.d 源代碼郵寄的討論 comp.sys.ibm.pc.hardware.comm PC機的調制解調器和通信卡 comp.sys.mac.portables Laptop的Macintosh機 comp.virus 計算機病毒和安全(有改動) comp.windows.x.i386unix XFree86窗口系統及其它
作法(Mechanics)
范例(Examples)
我們的范例其行為非常簡單:當用戶修改文本框中的數值,另兩個文本框就會自動更新.如果你修改Start或End,length就會自動成為兩者計算所得的長度;如果你修改length,End就會隨之變動. 一開始,所有函數都放在IntervalWindow class中.所有文本框都能夠響應[失去鍵盤焦點](loss of focus)這一事件。 public class IntervalWindow extends Frame... ??? java.awt.TextField _startField; ??? java.awt.TextField _endField; ??? java.awt.TextField _lengthField; ??? class SymFocus extends java.awt.event.FocusAdapter ??? { ??? ?? public void focusLost(java.awt.event.FocusEvent event) ??? ?? { ??? ?? ?? Object object = event.getSource(); ??? ??? ?? ?? if(object == _startField) ??? ?? ?? ?? StartField_FocusLost(event); ??? ?? ?? else if(object = _endField) ??? ?? ?? ?? EndField_FocusLost(event); ??? ?? ?? else if(object = _lengthField) ??? ?? ?? ?? LengthField_FocusLost(event); ??? ?? } } 當Start文本框失去焦點,事件監聽器調用StartField_FocusLost()。另兩個文本框的處理也類似。事件處理函數大致如下: void StartField_FocusLost(java.awt.event.FocusEvent event) { ??? if(isNotInteger(_startField.getText())) ??? ?? _startField.setText("0"); ??? calculateLength(); } void EndField_FocusLost(java.awt.event.FocusEvent event) { ??? if(isNotInteger(_endField.getText())) ??? ?? _endField.setText("0"); ??? calculateLength(); } void LengthField_FocusLost(java.awt.event.FocusEvent event) { ??? if(isNotInteger(_lengthField.getText())) ??? ?? _lengthField.setText("0"); ??? calculateLength(); } 如果文本框的字符串無法轉換為一個整數,那么該文本框的內容將變成0。而后,調用相關計算函數: void calculateLength() { ??? try { ??? ?? int start = Integer.parseInt(_startField.getText()); ??? ?? int end = Integer.parseInt(_endField.getText()); ??? ?? int length = end - start; ??? ?? _lengthField.setText(String.valueOf(length)); ??? } catch(NumberFormatException e) { ??? ?? throw new RuntimeException("Unexpected Number Format Error"); ??? } } void calculateEnd() { ??? try { ??? ?? int start = Integer.parseInt(_startField.getText()); ??? ?? int end = Integer.parseInt(_endField.getText()); ??? ?? int end = start + length; ??? ?? _endField.setText(String.valueOf(end)); ??? } catch(NumberFormatException e) { ??? ?? throw new RuntimeException("Unexpected Number Format Error"); ??? } } 我的任務就是非視覺性的計算邏輯從GUI中分離出來。基本上這就意味將calculateLength()和calculateEnd()移到一個獨立的domain class去。為了這一目的,我需要能夠在不引用窗口類的前提取用Start、End和length三個文本框的值。唯一辦法就是將這些數據復制到domain class中,并保持與GUI class數據同步。這就是Duplicate Observed Data(189)的任務。 截至目前我還沒有一個domain class,所以我著手建立一個: class Interval extends Observable {} IntervalWindow class需要與此嶄新的domain class建立一個關聯: private Interval _subject; 然后,我需要合理地初始化_subject值域,并把IntervalWindow class變成Interval class的一個Observer。這很簡單,只需把下列代碼放進IntervalWindow構造函數中就可以了: _subject = new Interval(); _subject.addObserver(this); update(_subject, null); 我喜歡把這段代碼放在整個建構過程的最后。其中對update()的調用可以確保:當我把數據復制到domain class后,GUI將根據domain class進行初始化。update()是在java.util.observer接口中聲明的,因此我必須讓IntervalWindow class實現這一接口: public class IntervalWindow extends Frame implements Observer 然后我還需要為IntervalWindow class建立一個update()。此刻我先令它為空: public void update(Observable observed, Object arg)? { } 現在我可以編譯并測試了。到目前為止我還沒有作出任何真正的修改。呵呵,小心駛得萬年船。 接下來我把注意力轉移到文本框。一如往常我每次只改動一點點。為了賣弄一下我的英語能力,我從End文本框開始。第一件要做的事就是實施Self Encapsulate Field(171)。文本框的更新是通過getText()和setText()兩函數實現的,因此我所建立的訪問函數(accessors)需要調用這兩個函數: String getEnd() { ??? return _endField.getText(); } void setEnd(String arg) { ??? _endField.setText(arg); } 然后,找出_endField的所有引用點,將它們替換為適當的訪問函數: void calculateLength() { ??? try { ??? ?? int start = Integer.parseInt(_startField.getText()); ??? ?? int end = Integer.parseInt(getEnd()); ??? ?? int length = end - start; ??? ?? _lengthField.setText(String.valueOf(length)); ??? } catch(NumberFormatException e) { ??? ?? throw new RuntimeException("Unexpected Number Format Error"); ??? } } void calculateEnd() { ??? try { ??? ?? int start = Integer.parseInt(_startField.getText()); ??? ?? int end = Integer.parseInt(_endField.getText()); ??? ?? int end = start + length; ??? ?? setEnd(String.valueOf(end)); ??? } catch(NumberFormatException e) { ??? ?? throw new RuntimeException("Unexpected Number Format Error"); ??? } } void EndField_FocusLost(java.awt.event.FocusEvent event) { ??? if(isNotInteger(getEnd())) ??? ?? setEnd("0"); ??? calculateLength(); } 這是Self Encapsulate Field(171)的標準過程。然后當你處理GUI class時,情況還更復雜些:用戶可以直接(通過GUI)修改文本框內容,不必調用setEnd()。因此我需要在GUI class的事件處理函數中加上對setEnd()的調用。這個動作把End文本框設定為其當前值。當然,這沒帶來什么影響,但是通過這樣的方式,我們可以確保用戶的輸入的確是通過設值函數(setter)進行的: void EndField_FocusLost(java.awt.event.FocusEvent event) { ??? setEnd(_endField.getText()); ??? if(isNotInteger(getEnd())) ??? ?? setEnd("0"); ??? calculateLength(); } 上述調用動作中,我并沒有使用上一頁的getEnd()取得End文 本框當前內容,而是直接取用該文本框。之所以這樣做是因為,隨后的重構將使上一頁的getEnd()從domain object(而非文本框)身上取值。那時如果這里用的是getEnd()函數,每當用戶修改文本框內容,這里就會將文本框又改回原值。所以我必須使用 [直接訪問文本框]的方式獲得當前值。現在我可以編譯并測試值域封裝后的行為了。 現在,在domain class中加入_end值域: class Interval... ??? private String _end = "0"; 在這里,我給它的初始值和GUI class給它的初值是一樣的。然后我再加入取值/設值函數(getter/setter): class Interval... ??? String getEnd() { ??? ?? return _end; ??? } ??? void setEnd(String arg) { ??? ?? _end = arg; ??? ?? setChanged(); ??? ?? notifyObservers(); ??? } 由于使用了Observer模式,我必須在設值函數(setter) 中加上[發出通告]動作(即所謂notification code)。我把_end聲明為一個字符串,而不是一個看似更合理的整數,這是因為我希望將修改量減至最少。將來成功復制數據完畢后,我可以自由自在地于 domain class內部把_end聲明為整數。 現在,我可以再編譯并測試一次。我希望通過所有這些預備工作,將下面這個較為棘手的重構步驟的風險降至最低。 首先,修改IntervalWindow class的訪問函數,令它們改用Interval對象: class IntervalWindow... ??? String getEnd() { ??? ?? return _subject.getEnd(); ??? } ??? void setEnd(String arg) { ??? ?? _subject.setEnd(arg); ??? } 同時也修改update()函數,確保GUI對Interval對象發來的通告做出響應: class IntervalWindow... ??? public void update(Observable observed, Object arg) { ??? ?? _endField.setText(_subject.getEnd()); ??? } 這是另一個需要[直接取用文本框]的地點。如果我調用的是設值函數(setter),程序將陷入無限遞歸調用(這是因為IntervalWindow的設 值函數setEnd()調用了Interval。setEnd(),一如稍早行所示:而Interval.setEnd()又調用 notifyObservers(),導致IntervalWindow.update()又被調用)。 現在,我可以編譯并測試,數據都恰如其分地被復制了。 另兩個文本框也如法炮制。完成之后,我可以使用Move Method(142)將calculateEnd()和calculateLength()搬到Interval class。這么一來,我就擁有一個[包容所有domain behavior和domain data]并與GUI code分離的domain class了。 如果上述工作都完成了,我就會考慮徹底擺脫這個GUI class。如果GUI class是個較為老舊的AWT class,我會考慮將它換成一個比較好看的Swing class,而且后者的坐標定位能力也比較強。我可以在domain class之上建立一個Swing GUI。這樣,只要我高興,隨時可以去掉老舊的GUI class。 使用事件監聽器(Event Listeners) 如果你使用事件監聽器(event listener)而不是Observer/Observable模式,仍然可以實施Duplicate Observed Data(189)。這種情況下,你需要在domain model中建立一個listener class和一個event class。然后,你需要對domain object注冊listeners,就像前例對observable對象注冊observers一樣。每當domain object發生變化(類似上例的update()函數被調用),就向listeners發送一個事件(event)。IntervalWindow class可以利用一個inner class(內嵌類)來實現監聽器接口(listener interface),并在適當時候調用適當的update()函數。
動機(Motivation)
一個分層良好的系統,應該將處理用戶界面(UI)和處理業務邏輯(business logic)的代碼分開。之所以這樣做,原因有以下幾點:(1)你可能需要使用數個不同的用戶界面來表現相同的業務邏輯;如果同時承擔兩種責任,用戶界面 會變得過分復雜;(2)與GUI隔離之后,domain objects的維護和演化都會更容易;你甚至可以讓不同的開發者負責不同部分的開發。 如果你遇到的代碼是以雙層(two-tiered)方式開發,業務邏輯被內嵌于用戶界面(UI)之中,你就有必要將行為分離出來。其中的主要工作就是函數的分離和搬移。但數據就不同了:你不能僅僅只是移動數據,你必須將它復制到新建部位中,并提供相應的同步機制。
注:所謂presentation class,用以處理[數據表現形式];所謂domain class,用以處理業務邏輯。
你有一些domain data置身于GUI控件中,而domain method需要訪問之。 將該筆數據拷貝一個domain object中。建立一個Observer模式,用以對domain object和GUI object內的重復數據進行同步控制(sync.)。 ![]() |