<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    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 閱讀(610) 評論(0)  編輯  收藏 所屬分類: refactoring-從地獄中重生
    主站蜘蛛池模板: 18成禁人视频免费网站| 国产亚洲精品美女| 全免费a级毛片免费看| 久久影视综合亚洲| 日韩大片免费观看视频播放| 国产成人免费永久播放视频平台| 67194在线午夜亚洲| 毛片A级毛片免费播放| 亚洲一卡一卡二新区无人区| 野花高清在线电影观看免费视频| 亚洲人成在线播放| 岛国av无码免费无禁网站| 亚洲色欲色欲www在线播放| 日韩精品视频免费网址| 羞羞视频网站免费入口| 精品国产成人亚洲午夜福利| 国产成人免费午夜在线观看| 久久久久国产成人精品亚洲午夜| 久久久久久久久久久免费精品| 中文字幕不卡亚洲 | 久久亚洲精品成人无码| 亚洲AV无码一区二区三区国产 | 人人狠狠综合久久亚洲婷婷| 免费视频成人手机在线观看网址| 亚洲激情在线观看| 无码国产精品一区二区免费式影视| 亚洲国产精品xo在线观看| 日韩免费在线观看| 中国精品一级毛片免费播放| 亚洲网红精品大秀在线观看| 成年18网站免费视频网站| 一级毛片免费在线| 免费观看毛片视频| 日本视频免费观看| 久久精品亚洲综合一品| 毛片免费观看视频| 中文字幕免费在线看| 亚洲午夜国产精品| 亚洲第一区精品观看| 最好看最新的中文字幕免费| 国产精品无码亚洲一区二区三区|