ivaneeo's blog

          自由的力量,自由的生活。

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            669 Posts :: 0 Stories :: 64 Comments :: 0 Trackbacks
          范例(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去。為了這一目的,我需要能夠在不引用窗口類的前提取用StartEndlength三個文本框的值。唯一辦法就是將這些數據復制到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()函數,確保GUIInterval對象發來的通告做出響應:
          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()函數。
          posted on 2005-09-06 11:07 ivaneeo 閱讀(605) 評論(0)  編輯  收藏 所屬分類: refactoring-從地獄中重生
          主站蜘蛛池模板: 沾化县| 石景山区| 德格县| 辽源市| 湖口县| 东港市| 米易县| 长岭县| 特克斯县| 青浦区| 江口县| 司法| 太仆寺旗| 潜山县| 平果县| 洛浦县| 扎赉特旗| 公主岭市| 新丰县| 福清市| 嘉荫县| 行唐县| 吐鲁番市| 大宁县| 温宿县| 怀化市| 西林县| 合肥市| 柳林县| 黔南| 巴东县| 沛县| 冕宁县| 马鞍山市| 朔州市| 文成县| 都江堰市| 探索| 鄄城县| 义马市| 洛浦县|