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分离的领域类了。

相关文章
相关标签/搜索