<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### Duplicate Observed Data(復制「被監視數據」) (譯注:本節大量保留domain,presentation,event,getter/setter,observed等字眼。所謂presentation class,用以處理「數據表現形式」;所謂domain class,用以處理業務邏輯。) 你有一些domain class置身于GUI控件中,而domain method需要訪問之。 將該筆數據拷貝到一個domain object中。建立一個Observer模式,用以對domain object和GUI object內的重復數據進行同步控制(sync.)。 ![](https://box.kancloud.cn/2016-08-15_57b1b56d175ab.gif) **動機(Motivation)** 一個分層良好的系統,應該將處理用戶界面(UI)和處理業務邏輯(business logic)的代碼分開。之所以這樣做,原因有以下幾點:(1) 你可能需要使用數個不同的用 戶界面來表現相同的業務邏輯;如果同時承擔兩種責任,用戶界面會變得過分復雜; (2) 與GUI隔離之后,domain class的維護和演化都會更容易;你甚至可以讓不同的開發者負責不同部分的開發。 盡管你可以輕松地將「行為」劃分到不同部位,「數據」卻往往不能如此。同一筆 數據有可能既需要內嵌于GUI控件,也需要保存于domain model里頭。自從MVC(Model-View-Controller)模式出現后,用戶界面框架都使用多層系統(multitiered system)來提供某種機制,使你不但可以提供這類數據,并保持它們同步(sync.)。 如果你遇到的代碼是以雙層(two-tiered)方式開發,業務邏輯(business logic)被內嵌于用戶界面(UI)之中,你就有必要將行為分離出來。其中的主要工作就是函數的分解和搬移。但數據就不同了:你不能僅僅只是移動數據,你必須將它復制到新建部位中,并提供相應的同步機制。 **作法(Mechanics)** (譯注:建議搭配范例閱讀) - 修改presentation class,使其成為 domain class 的 Observer[GoF]。 - 如果尚未有domain class,就建立一個。 - 如果沒有「從presentation class到domain class的關聯性(link), 就將domain class保存于咖presentation class的一個值域中。 - 針對GUI class內的domain data,使用Self Encapsulate Field 。 - 編譯,測試。 - 在事件處理函數(event handler)中加上對設值函數(setter)的調用,以「直接訪問方式」(譯注:亦即直接調用組件提供的相關函數)更新GUI組件。 - 在事件處理函數中放一個設值函數(setter),利用它將GUI組件更新為domain data的當前值。當然這其實沒有必要,你只不過是拿它的值設定它自己。但是這樣使用setter,便是允許其中的任何動作得以于日后被執行起來,這是這一步驟的意義所在。 - 進行這個改變時,對于組件,不要使用取值函數(getter),應該采取「直接取用」方式(譯注:亦即直接調用GUI組件所提供的函數),因為稍后我們將修改取值函數(getter),使其從domain object(而非GUI組件)取值。設值函數(setter)也將遭受類似修改。 - 確保測試代碼能夠觸發新添加的事件處理(event handler)機制。 - 編譯,測試。 - 在domain class中定義數據及其相關訪問函數(accessors)。 - 確保domain class中的設值函數(setter)能夠觸發Observer模式的通報機制(notify mechanism)。 - 對于被觀察(被監視)的數據,在domain class中使用「與presentation class所用的相同型別」(通常是字符串)來保存。后續重構中你可以自由改變這個數據型別。 - 修改presentation class中的訪問函數(accessors),將它們的操作對象改為 domain object (而非GUI組件)。 - 修改observer(譯注:亦即presentation class)的update(),使其從相應的domain object中將所需數據拷貝給GUI組件。 - 編譯,測試。 **范例(Example)** 我們的范例從圖8.1所示窗口開始。其行為非常簡單:當用戶修改文本框中的數值,另兩個文本框就會自動更新。如果你修改Start或End,length就會自動成為兩者計算所得的長度;如果你修改length,End就會隨之改變。 ![](https://box.kancloud.cn/2016-08-15_57b1b56d3065e.gif) 圖8.1 一個簡單的GUI窗口 一開始,所有函數都放在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(); //譯注:偵測到哪一個文本框失去鍵盤焦點,就調用其event-handler. 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"); calculateEnd(); } ~~~ 你也許會奇怪,為什么我這樣實現一個窗口呢?因為在我的IDE集成開發環境(Cafe)中,這是最簡單的方式。 如果文本框內的字符串無法轉換為一個整數,那么該文本框的內容將變成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 length = Integer.parseInt(_lengthField.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去。為了這一目的,我需要能夠在不引用(指涉,referring)窗口類的前提下取用Start、End和 length 三個文本框的值。惟一辦法就是將這些數據復制到domain class中,并保持與GUI class數據同步。這就是Duplicate Observed Data 的任務。 截至目前我還沒有一個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。文本框的更新是通過getText()和setText()兩函數實現的,因此我所建立的訪問函數(accessors)需要調用這兩個函數: ~~~ //譯注:class IntervalWindow... 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 length = Integer.parseInt(_lengthField.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 的標準過程。然而當你處理GUI class 時,情況還更復雜些:用戶可以直接(通過GUI )修改文本框內容,不必調用setEnd() 。 因此我需要在GUI class 的事件處理函數中加上對setEnd() 的調用。這個動作把文本框設定為其當前值。當然,這沒帶來什么影響,但是通過這樣的方式,我 們可以確保用戶的輸入的確是通過設值函數(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(); //譯注:notificaiton code } ~~~ 由于使用了Observer 模式,我必須在設值函數(setter)中加上「發出通告」動作 (即所謂notify code )。我把_end 聲明為一個字符串,而不是一個看似更合理的整數,這是因為我希望將修改量減至最少。將來成功復制數據完畢后,我可以自由自在地于domain class 內部把_end 聲明為整數。 現在,我可以再編譯并測試一次。我希望通過所有這些預備工作,將下面這個較為棘手的重構步驟的風險降至最低。 首先,修改IntervalWindow class 的訪問函數,令它們改用Interval 對象: ~~~ class IntervalWindow... String getEnd() { return _subject.getEnd(); } void setEnd (String arg) { _subject.setEnd(arg); //(A) 譯注:本頁最下對此行有些說明 } ~~~ 同時也修改update() 函數,確保GUI 對Interval 對象發來的通告做出響應: ~~~ class IntervalWindow... public void update(Observable observed, Object arg) { _endField.setText(_subject.getEnd()); } ~~~ 這是另一個需要「直接取用文本框」的地點。如果我調用的是設值函數(setter),程序將陷入無限遞歸調用(譯注:這是因為IntervalWindow 的設值函數setEnd() 調用了Interval.setEnd() ,一如稍早(A)行所示;而Interval.setEnd() 又調用notifyObservers() ,導致IntervalWindow.update() 又被調用)。 現在,我可以編譯并測試。數據都恰如其分地被復制了。 另兩個文本框也如法炮制。完成之后,我可以使用Move Method 將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。這種情況下,你需要在domain model中建立一個listener class和一個event class (如果你不在意依存關系的話,也可以使用AWT class)。然后,你需要對domain object注冊listeners,就像前例對observable對象注冊observes一樣。每當domain object發生變化(類似上例的update()函數被調用),就向listeners發送一個事件(event)。IntervalWindow class可以利用一個inner class (內嵌類)來實現監聽器接口(listener interface),并在適 當時候調用適當的update()函數。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看