Duplicate Observed Data (複製「被監視數據」)

Summary:

有一些領域數據置身於GUI控件中,而領域函數須要訪問這些數據。將該數據複製到一個領域對象中。創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據。 java

Motivation:

一個分層良好的系統,應該將處理用戶界面和處理業務邏輯的代碼分開。之因此這樣作,緣由有一下幾點:(1)你可能須要使用不一樣的用戶界面來表現相同的業務邏輯,若是同時承擔兩種責任,用戶界面會變得過度複雜;(2)與GUI隔離後,領域對象的維護和演化都會更容易,甚至可讓不一樣的開發者負責不一樣部分的開發。 框架

儘管能夠輕鬆地將「行爲」劃分到不一樣部位,「數據」卻每每不能如此。同一項數據有可能急須要內嵌於GUI控件,也須要保存於領域模型裏。自從MVC模式出現後,用戶界面框架都是用多層系統來提供某種機制,使你不但能夠提供這類數據,並保持他們同步。 函數

若是代碼是以兩層方式開發,業務邏輯被內嵌於用戶界面之中,就有必要將行爲分離出來。其中的主要工做就是函數的分解和搬移。但數據就不一樣了:不能僅僅只是移動數據,必須將它複製到新對象中,並提供相應的同步機制。 測試

Mechanics: 

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分離的領域類了。

相關文章
相關標籤/搜索