有一些領域數據置身於GUI控件中,而領域函數須要訪問這些數據。將該數據複製到一個領域對象中。創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據。 java
一個分層良好的系統,應該將處理用戶界面和處理業務邏輯的代碼分開。之因此這樣作,緣由有一下幾點:(1)你可能須要使用不一樣的用戶界面來表現相同的業務邏輯,若是同時承擔兩種責任,用戶界面會變得過度複雜;(2)與GUI隔離後,領域對象的維護和演化都會更容易,甚至可讓不一樣的開發者負責不一樣部分的開發。 框架
儘管能夠輕鬆地將「行爲」劃分到不一樣部位,「數據」卻每每不能如此。同一項數據有可能急須要內嵌於GUI控件,也須要保存於領域模型裏。自從MVC模式出現後,用戶界面框架都是用多層系統來提供某種機制,使你不但能夠提供這類數據,並保持他們同步。 函數
若是代碼是以兩層方式開發,業務邏輯被內嵌於用戶界面之中,就有必要將行爲分離出來。其中的主要工做就是函數的分解和搬移。但數據就不一樣了:不能僅僅只是移動數據,必須將它複製到新對象中,並提供相應的同步機制。 測試
1.修改展示類,使其成爲領域類的Observer this
若是還沒有有領域類,就創建一個。 spa
若是沒有「從展示類到領域類」的關聯,就將領域類保存於展示類的一個字段中。 code
2.針對GUI類中的領域數據,使用Self Encapsulate Field。 orm
3.編譯,測試。 server
4.在事件處理函數中調用設值函數,直接更新GUI組件。 對象
在事件處理函數中放一個設置函數,利用它將GUI組件更新爲領域數據的當前值。固然這樣其實沒有必要你只不過是拿它的值設定它本身。可是這樣使用設值函數,即是容許其中的任何動做得以於往後被執行起來,這是這一步驟的意義所在。
進行這個改變時,對於組件,不要使用取值函數,應該直接取用,由於稍後咱們將修改取值函數,使其從領域對象(而非GUI組件)取值。設置函數也將作相似修改。
確保測試代碼可以觸發新添加的事件處理機制。
5.編譯,測試。
6.在領域類中定義數據及其相關訪問函數。
確保領域類中的設值函數可以觸發Observer模式的同步機制。
對於被觀察的數據,在領域類中使用與展示類所用的相同類型(一般是字符串)來保存。後續重構中能夠自由改變這個數據類型。
7.修改展示類中的訪問函數,將它們的操做對象改成領域對象(而非GUI組件)
8.修改Observer的updated () ,使其從相應的領域對象中所需數據複製給GUI組件
9. 編譯,測試
咱們的範例從下圖所示的窗口開始。其行爲很是簡單:當用戶修改文本框中的數值,另兩個文本框就會自動更新。若是你修改Start或End,Length就會自動成爲二者計算所得的長度;若是修改Length,End就會隨之變更。
一開始,全部的函數都放在IntervalWindow類中。全部文本框都可以相應「失去焦點」這一事件。
public class IntervalWindow extends Frame { TextField startField; TextField endField; TextField lengthField; class SysFocus extends FocusAdapter { public void focusLost( FocusEvent event ) { Object object = event.getSource(); if( object == startField ) { startFieldFocusLost( event ); } else if( object == endField ) { endFieldFocusLost( event ); } else if( object == lengthField ) { lengthFieldFocusLost( event ); } } }
當Start文本框失去焦點,事件監聽器調用startFieldFocusLost().另兩個文本框處理也相似。事件函數大體以下:
void startFieldFocusLost( FocusEvent event ) { if( isNotInteger( startField.getText() ) ) { startField.setText( "0" ); } caculateLength(); } void endFieldFocusLost( FocusEvent event ) { if( isNotInteger( endField.getText() ) ) { endField.setText( "0" ); } caculateLength(); } void lengthFieldFocusLost( FocusEvent event ) { if( isNotInteger( lengthField.getText() ) ) { lengthField.setText( "0" ); } caculateEnd(); } void caculateLength() { 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 caculateEnd() { 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移到一個獨立的領域類去。爲了這一目的,咱們須要可以在不引用窗口類的前提下獲取Start、End和Length三個文本框的值。惟一的辦法就是將這些數據複製到領域類中,並保持與GUI數據同步。這就是Duplicate Observed Data的任務。
截至目前,咱們尚未一個領域類,因此要着手創建一個
public class Interval extends Observable { }IntervalWindow類須要與此嶄新的領域類創建一個關聯:
private Interval subject;而後,合理的初始化subject字段,並把IntervalWindow變成Interval的一個Observer。這很簡單,只需把下列代碼放進IntervalWindow構造函數中就能夠了:
subject = new Interval(); subject.addObserver( this ); update( subject, null );其中對update的調用能夠確保:當咱們把數據複製到領域類後,GUI將根據領域類進行初始化。update()是在java.util.Observer接口中聲明的,所以必須讓IntervalWindow實現這一接口, 而後爲IntervalWindow類創建一個update().
接下來,咱們開始修改文本框。從End開始。先運用Self Encapsulate Field.文本框的更新時經過getText()和setText()兩函數實現的,所以咱們所創建的訪問函數須要調用這兩個函數:
String getEnd() { return endField.getText(); } void setEnd( String arg ) { endField.setText( arg ); }而後,找出endField的全部引用點,將他們替換爲適當的訪問函數。這是Self Encapsulate Field的標準過程。然而檔處理GUI時,狀況更復雜些:用戶能夠直接(經過GUI)修改文本框內容,沒必要調用setEnd()。所以咱們須要在GUI的事件處理中調用setEnd()。這個動做把End文本框設定爲其當前值。固然,這沒帶來什麼影響,可是經過這樣的方式,能夠確保用戶的輸入確實是經過設置函數進行的:
void endFieldFocusLost( FocusEvent event ) { setEnd( endField.getText() ); if( isNotInteger( getEnd() ) ) { setEnd( "0" ); } caculateLength(); }
上述動做中,咱們並無使用前面的getEnd()取得End文本框當前內容,而是直接訪問文本框。之因此這樣作是由於,隨後的重構將是getEnd()從領域對象身上取值。那是若是這裏調用的是getEnd()函數,每當用戶修改文本框內容,這裏就會將文本框內容又改回原值。因此必須使用直接訪問文本框的方式獲取當前值。如今咱們能夠編譯並測試字段封裝後的行爲了。
如今,在領域類中加入end字段,給它的初始值和GUI給的初始值是同樣的。而後再 加入取值/設值函數:
public class Interval extends Observable { private String end = "0"; public String getEnd() { return end; } public void setEnd( String end ) { this.end = end; setChanged(); notifyObservers(); } }
因爲使用了Observer模式,咱們必須在設值函數中發出通知。
如今咱們修改IntervalWindow類的訪問函數,令它們改用Interval對象:
String getEnd() { return subject.getEnd(); } void setEnd( String arg ) { subject.setEnd( arg ); }
同時也修改update()函數,確保GUI對Interval對象發來的通知作出響應:
public void update( Observable arg0, Object arg1 ) { endField.setText( subject.getEnd() ); }
這是另外一個須要直接訪問文本框的地點。若是咱們調用的是設值函數,程序將陷入無限遞歸調用。
如今,咱們能夠編譯並測試。數據都恰如其分地被複制了。
另兩個文本框也如法炮製。完成以後,咱們可使用Move Method將calculateEnd()和calculateLength()搬到Interval 去。這麼一來,咱們就擁有一個包容全部領域行爲和領域數據、並與GUI分離的領域類了。