有一些领域数据置身于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分离的领域类了。