﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-ivaneeo's blog-随笔分类-refactoring－从地狱中重生</title><link>http://www.blogjava.net/ivanwan/category/2732.html</link><description>自由的力量，自由的生活。</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 18:28:30 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 18:28:30 GMT</pubDate><ttl>60</ttl><item><title>重构三--重新组织数据（Encapsulate Collection）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/19/13396.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 19 Sep 2005 05:51:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/19/13396.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13396.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/19/13396.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13396.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13396.html</trackback:ping><description><![CDATA[<P><SPAN style="FONT-WEIGHT: bold"><FONT size=5>范例</FONT></SPAN>（Examples）<BR>Java 2拥有一组全新群集(collections)--并非仅仅加入一些新classes,而是完全改变了群集的风格.所以在<STRONG>Java 1.1</STRONG>和<STRONG>Java 2</STRONG>中,封装群集的方式也完全不同.我首先讨论<STRONG>Java 2</STRONG>的方式,因为我认为功能更强大的<STRONG>Java 2 collections</STRONG>会取代<STRONG>Java 1.1 collections</STRONG>的地位.<BR><BR><SPAN style="FONT-WEIGHT: bold"><FONT size=5>范例</FONT></SPAN>（Examples）: <STRONG>Java 2<BR><BR></STRONG>假设有个人要去上课.我们用一个简单的Course来表示[课程]:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Course...<BR>&nbsp;&nbsp;&nbsp;public Course(String name, boolean isAdvanced) {...};<BR>&nbsp;&nbsp;&nbsp;public boolean isAdvanced() {...};</FONT><BR>我不关心课程其他细节.我感兴趣的是表示[人]的<STRONG>Person</STRONG>:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Person...<BR>&nbsp;&nbsp;&nbsp;public Set getCourse() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return _courses;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;public void setCourse(Set arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_courses = arg;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;private Set _courses;</FONT><BR>有了这个接口,我们就可以这样为某人添加课程:<BR>&nbsp;&nbsp;&nbsp;<FONT style="BACKGROUND-COLOR: #d3d3d3">Person kent = new Person();<BR>&nbsp;&nbsp;&nbsp;Set s = new HashSet();<BR>&nbsp;&nbsp;&nbsp;s.add(new Course("Smalltalk Programming", false));<BR>&nbsp;&nbsp;&nbsp;s.add(new Course("Appreciating Single Malts", true));<BR>&nbsp;&nbsp;&nbsp;kent.setCourses(s);<BR>&nbsp;&nbsp;&nbsp;Assert.equals(2, Kent.getCourses().size());<BR><BR>&nbsp;&nbsp;&nbsp;Course refact = new Course("Refactoring", true);<BR>&nbsp;&nbsp;&nbsp;kent.getCourses().add(refact);<BR>&nbsp;&nbsp;&nbsp;kent.getCourses().add(new Course("Brutal Sarcasm", false));<BR>&nbsp;&nbsp;&nbsp;Assert.equals(4, kent.getCourses().size());</FONT></P>
<P><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;kent.getCourses().remove(refact);<BR>&nbsp;&nbsp;&nbsp;Assert.equals(3, kent.getCourses().size());<BR></FONT>如果想了解高级课程,可以这么做:<BR>&nbsp;<FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;Iterator iter = person.getCourses().iterator();<BR>&nbsp;&nbsp;&nbsp;int count = 0;<BR>&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Course each = (Course)iter.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(each.isAdvanced()) count++;<BR>&nbsp;&nbsp;&nbsp;}<BR></FONT>我要做的第一件事就是为Person中的群集(collections)建立合适的修改函数(modifiers, 亦即add/remove函数),如下所示,然后编译:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Person...<BR>&nbsp;&nbsp;&nbsp;public void addCourse(Course arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_courses.add(arg);<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;public void removeCourse(Course arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_courses.remove(arg);<BR>&nbsp;&nbsp;&nbsp;}</FONT><BR>如果我想下面这样初始化_courses值域,我的人生会轻松得多:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;private Set _courses = new HashSet();</FONT><BR><BR>接下来我需要观察设值函数(setter)的调用者.如果有许多地点大量运用了设值函数,我就需要修改设值函数,令它调用添加/移除(add/remove)函数.这个过程的复杂度取决于设值函数的被使用方式.设值函数的用法有两种,最简单的情况就是:它被用来[对集群进行初始化动作].换句话说,设值函数被调用之前,_courses是个空群集.这种情况下我需要修改设值函数,令它调用添加函数(add)就行了:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Person...<BR>&nbsp;&nbsp;&nbsp;public void setCourses(Set arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Assert.isTrue(_courses.isEmpty());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator iter = arg.iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addCourse((Course)iter.next());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;}<BR></FONT>修改完毕后,最后以<STRONG>Rename Method</STRONG>(273)更明确地展示这个函数的意图.<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;public void <STRONG>initializeCourses</STRONG>(Set arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Assert.isTrue(_courses.isEmpty());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator iter = arg.iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addCourse((Course)iter.next());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;}</FONT><BR>更普通的情况下,我必须首先以移除函数(remove)将群集中的所有元素全部移除,然后再调用添加函数(add)将元素一一添加进去.不过我发现这种情况很少出现(唔,愈是普通的情况,愈少出现).<BR><BR>如果我知道初始化时,除了添加元素,不会再有其他行为,那么我可以不使用循环,直接调用addAll()函数:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;public void initializeCourses(Set arg) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Assert.isTrue(_courses.isEmpty());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</FONT><FONT style="BACKGROUND-COLOR: #d3d3d3"><STRONG>_courses.addAll(arg);<BR></STRONG>&nbsp;&nbsp;&nbsp;}</FONT><BR>我不能仅仅对这个set赋值,就算原本这个set是空的也不行.因为万一用户在[把set传递给Person对象]之后又去修改它,会破坏封装.我必须像上面那样创建set的一个拷贝.<BR><BR>如果用户仅仅只是创建一个set,然后使用设值函数(setter.译注:目前已改名为initializeCourses()),我可以让它们直接使用添加/移除(add/remove)函数,并将设值函数完全移除.于是,以下代码:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;Person kent = new Person();<BR>&nbsp;&nbsp;&nbsp;Set s = new HashSet();<BR>&nbsp;&nbsp;&nbsp;s.add(new Course("Smalltalk Programming", false));<BR>&nbsp;&nbsp;&nbsp;s.add(new Course("Appreciating Single Malts", true));<BR>&nbsp;&nbsp;&nbsp;kent.initializeCourses(s);</FONT><BR>就变成了:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;Person kent = new Person();<BR><STRONG>&nbsp;&nbsp;&nbsp;kent.addCourse</STRONG>(new Course("Smalltalk Programming", false));<BR>&nbsp;&nbsp;&nbsp;<STRONG>kent.addCourse</STRONG>(new Course("Appreciating Single Malts", true));</FONT><BR><BR><BR>接下来我开始观察取值函数(getter)的使用情况.首先处理[有人以取值函数修改底部群集(underlying collection)]的情况,例如:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;kent.getCourses().add(new Course("Brutal Sarcasm", false));</FONT><BR>这种情况下我必须加以改变,使它调用新的修改函数(<STRONG>modifier</STRONG>):<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;kent.<STRONG>addCourse</STRONG>(new Course("Brutal Sarcasm", false));</FONT><BR>修改完所有此类情况之后,我可以让取值函数(getter)返回一个只读映件(read-only view),用以确保没有任何一个用户能够通过取值函数(getter)修改群集:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;public Set getCourses() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Collections.unmodifiableSet(_courses);<BR>&nbsp;&nbsp;&nbsp;}<BR></FONT>这样我就完成了对群集的封装.此后,不通过Person提供的add/remove函数,谁也不能修改群集内的元素.<BR><BR><FONT size=5><STRONG>将行为移到这个class中<BR><BR></STRONG></FONT><FONT size=3>我拥有了合理的接口.现在开始观察取值函数(getter)的用户,从中找出应该属于<STRONG>Person</STRONG>的代码.下面这样的代码就应该搬移到<STRONG>Person</STRONG>去:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;Iterator iter = person.getCourses().iterator();<BR>&nbsp;&nbsp;&nbsp;int counter = 0;<BR>&nbsp;&nbsp;&nbsp;while(iter.hasNext())｛<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Course each = (Course)iter.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(each.isAdvanced()) cout++;<BR>&nbsp;&nbsp;&nbsp;}<BR></FONT><BR>因为以上只使用了属于<STRONG>Person</STRONG>的数据.首先我使用<STRONG>Extract Method</STRONG>(110)将这段代码提炼为一个独立函数:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;int numberOfAdvancedCourses(Person person) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator iter = person.getCourses().iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int count = 0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Course each = (Course)iter.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(each.isAdvanced()) count++;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return count;<BR>&nbsp;&nbsp;&nbsp;}<BR></FONT>然后使用<STRONG>Move Method</STRONG>(142)将这个函数搬移到<STRONG>Person</STRONG>中:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Person...<BR>&nbsp;&nbsp;&nbsp;int numberOfAdvancedCourses(Person person) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator iter = person.getCourses().iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int count = 0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Course each = (Course)iter.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(each.isAdvanced()) count++;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return count;<BR>&nbsp;&nbsp;&nbsp;}</FONT><BR>举个常见例子,下列代码:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">&nbsp;&nbsp;&nbsp;kent.getCourses().size();</FONT><BR>可以修改更具可读性的样子,像这样:<BR><BR><FONT style="BACKGROUND-COLOR: #d3d3d3">kent.numberOfCourses();<BR><BR>class Person...<BR>public int numberOfCourses() {<BR>&nbsp;&nbsp;&nbsp;return _courses.size();<BR>}</FONT><BR>数年以前,我曾经担心将这样的行为搬移到<STRONG>Person</STRONG>中会导致<STRONG>Person</STRONG>变得臃肿.但是在实际工作经验中,我发现这通常并不成为问题.</FONT></P><img src ="http://www.blogjava.net/ivanwan/aggbug/13396.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-19 13:51 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/19/13396.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Collection）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13102.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 09:51:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13102.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13102.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13102.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13102.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13102.html</trackback:ping><description><![CDATA[<UL>
<LI><SPAN style="FONT-WEIGHT: bold"><FONT size=6>作法</FONT></SPAN>（Mechanics) </LI>
<UL>
<LI>加入[为群集添加(add),移除(remove)元素]的函数. 
<LI>将[用以保存群集]的值域初始化为一个空群集. 
<LI>编译. 
<LI>找出[群集设值函数]的所有调用者.你可以修改那个设值函数,让它使用上述新建立的[添加/移除元素]函数;也可以直接修改调用端,改让它们调用上述新建立的[添加/移除元素]函数. 
<UL>
<UL>
<LI>==&gt;两种情况下需要用到[群集设值函数];(1)群集为空时;(2)准备将原有群集替换为另一个群集时. 
<LI>==&gt;你或许会想运用<STRONG>Rename Method</STRONG>(273)为[群集设值函数]改名,从setXxx()改为initializeXxx()或replaceXxx().</LI></UL></UL>
<LI>编译,测试. 
<LI>找出所有[通过取值函数(getter)获得群集并修改其内容]的函数.逐一修改这些函数,让它们改用[添加/移除](add/remove)函数.每次修改后,编译并测试. 
<LI>修改完上述所有[通过取值函数(getter)获得群集并修改群集内容]的函数后,修改取值函数自身,使它返回该群集的一个只读映件(read-only view).</LI>
<UL>
<UL>
<LI>==&gt;在Java 2中,你可以使用Collection.unmodifiableXxx()得到该集群的只读映件. 
<LI>==&gt;在Java 1.1中,你应该返回群集的一份拷贝.</LI></UL></UL>
<LI>编译,测试. 
<LI>找出取值函数(getter)的所有用户,从中找出应该存在于[群集之宿主对象(host object)]内的代码.运用<STRONG>Extract Method</STRONG>(110)和<STRONG>Move Method</STRONG>(142)将这些代码移到宿主对象去.</LI></UL>
<LI>如果你使用Java 2,那么本项重构到此为止.如果你使用Java 1.1,那么用户也许会喜欢使用枚举(enumeration).为了提供这个枚举,你应该这样做.</LI>
<UL>
<LI>修改现有取值函数(getter)的名字,然后添加一个新取值函数,使其返回一个枚举.找出旧取值函数的所有被使用点,将它们都改为使用新取值函数.</LI>
<UL>
<UL>
<LI>==&gt;如果这一步跨度太大,你可以先使用<STRONG>Rename Method</STRONG>(273)修改原取值函数的名称;再建立一个新取值函数用以返回枚举;最后再修改所有调用者,使其调用新取值函数.</LI></UL></UL>
<LI>编译,测试.</LI></UL></UL><img src ="http://www.blogjava.net/ivanwan/aggbug/13102.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 17:51 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13102.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Collection）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13085.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 07:35:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13085.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13085.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13085.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13085.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13085.html</trackback:ping><description><![CDATA[<SPAN style="FONT-WEIGHT: bold"><FONT size=6>动机</FONT></SPAN>（Motivation）<BR>class常常会使用群集(collection,可能是array,list,set或vector)来保存一组实体.这样的class通常也会提供针对该群集[取值/设值函数](getter/setter).<BR><BR>但是,集群的处理方式应该和其他种类的数据略有不同.取值函数(getter)不该返回群集自身,因为这将让用户得以修改群集内容而群集拥有者却一无所悉.这也会对用户暴露过多[对象内部数据结构]的信息.如果一个取值函数(getter)确实需要返回多个值,它应该避免用户直接操作对象内所保存的群集,并隐藏对象内[与用户无关]的数据结构.至于如何做到这一点,视你使用的Java版本不同而有所不同.<BR><BR>另外,不应该为这整个群集提供一个设值函数(setter),但应该提供用以为群集添加/移除(add/remove)元素的函数.这样,群集拥有者(对象)就可以控制群集元素的添加和移除.<BR><BR>如果你做到以上数点,群集(collection)就被很好地封装起来了,这便可以降低群集拥有者(class)和用户之间的耦合度.<img src ="http://www.blogjava.net/ivanwan/aggbug/13085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 15:35 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Collection）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13082.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 07:08:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13082.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13082.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13082.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13082.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13082.html</trackback:ping><description><![CDATA[有个函数(<STRONG>method</STRONG>)返回一个群集(<STRONG>collection</STRONG>).<BR><BR><STRONG>让这个函数返回该群集的一个只读映件(read-only view),并在这个class中提供[添加/移除](add/remove)群集元素的函数.</STRONG><BR><BR><IMG height=113 alt="Encapsulate Collection.jpg" src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/Encapsulate%20Collection.jpg" width=421 border=0><img src ="http://www.blogjava.net/ivanwan/aggbug/13082.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 15:08 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13082.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Field）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13074.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 06:30:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13074.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13074.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13074.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13074.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13074.html</trackback:ping><description><![CDATA[<UL>
<LI><SPAN style="FONT-WEIGHT: bold"><FONT size=6>作法</FONT></SPAN>（Mechanics)</LI>
<UL>
<LI>为public值域提供取值/设值函数(getter/setter).</LI>
<LI>找到这个class以外使用该值域的所有地点.如果客户只是使用该值域,就把引用动作(reference)替换为对取值函数(getter)的调用];如果客户修改了该值值域,就将此一引用点替换为[对设值函数(setter)的调用].</LI>
<UL>
<UL>
<LI>==&gt;如果这个值域是个对象,而客户只不过是调用该对象的某个函数,那么不论该函数是否为修改函数(modifier,会改变对象状态),都只能算是使用该值域.只有当客户为该值域赋值时,才能将其替换为设值函数(setter).</LI></UL></UL>
<LI>每次修改之后,编译并测试.</LI>
<LI>将值域的所有用户修改完毕后,把值域声明为private.</LI>
<LI>编译,测试.</LI></UL></UL><img src ="http://www.blogjava.net/ivanwan/aggbug/13074.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 14:30 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13074.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Field）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13073.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 06:21:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13073.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13073.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13073.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13073.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13073.html</trackback:ping><description><![CDATA[<SPAN style="FONT-WEIGHT: bold"><FONT size=6>动机</FONT></SPAN>（Motivation）<BR>面向对象的首要原则之一就是封装(encapsulation),或者称为[数据隐藏](data hidding).<BR><BR>public数据被看做是一种不好的作法,因为这样会降低程序的模块化程度(modularity).如果数据和使用该数据的行为被集中在一起,一旦情况发生变化,代码的修改就会比较简单,因为需要修改的代码都集中于同一块地方,而不是星罗棋布地散落在整个程序中.<BR><BR><STRONG>Encapsulate Field</STRONG>(206)是封装过程的第一步.通过这项重构手法,你可以将数据隐藏起来,并提供相应的访问函数(accessors).但它毕竟只是第一步.如果一个class除了访问函数(<STRONG>accessors</STRONG>)外不能提供其他行为,它终究只是一个<STRONG>dumb class</STRONG>(哑类).这样的class并不能获得对象技术的优势,而你知道,浪费如何一个对象都是很不好的.实施<STRONG>Encapsulate Field</STRONG>(206)之后,我会尝试寻找那些使用[新建访问函数]的函数,看看是否可以通过简单的<STRONG>Move Method</STRONG>(142)轻快地将它们移到新对象去.<img src ="http://www.blogjava.net/ivanwan/aggbug/13073.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 14:21 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13073.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Encapsulate Field）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13069.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 06:07:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13069.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13069.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13069.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13069.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13069.html</trackback:ping><description><![CDATA[你的class中存在一个public值域.<BR><BR><STRONG>将它声明为private,并提供相应的访问函数(accessors).<BR></STRONG><BR><FONT style="BACKGROUND-COLOR: #d3d3d3">public String _name</FONT><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <IMG height=41 alt=126.gif src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/126.gif" width=45 border=0><BR><FONT style="BACKGROUND-COLOR: #d3d3d3">private String _name;<BR>public String getName() {return _name;}<BR>public void setName(String arg) {_name = arg;}</FONT><img src ="http://www.blogjava.net/ivanwan/aggbug/13069.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 14:07 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13069.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Magic Number with Symbolic Constant）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13068.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 06:01:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13068.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13068.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13068.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13068.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13068.html</trackback:ping><description><![CDATA[<UL>
<LI><SPAN style="FONT-WEIGHT: bold"><FONT size=6>作法</FONT></SPAN>（Mechanics)<BR></LI>
<UL>
<LI>声明一个常量,令其值为原本的魔法数值.</LI>
<LI>找出这个魔法数的所有引用点.</LI>
<LI>检查是否可以使用这个新声明的常量来替换该魔法数.如果可以,便以一常量替换之.</LI>
<LI>编译.</LI>
<LI>所有魔法数都被替换完毕后,编译并测试.此时整个程序应该运转如常,就像没有做任何修改一样.</LI>
<UL>
<UL>
<LI>==&gt;有个不错的测试办法:检查现在的程序是否可以被你轻松地修改常量值(这可能意味某些预期结果将有所改变,以配合这一新值.实际工作中并非总是可以进行这样的测试).如果可行,这就是一个不错的手法.</LI></UL></UL></UL></UL><img src ="http://www.blogjava.net/ivanwan/aggbug/13068.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 14:01 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13068.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Magic Number with Symbolic Constant）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13065.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 05:54:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13065.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13065.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13065.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13065.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13065.html</trackback:ping><description><![CDATA[<SPAN style="FONT-WEIGHT: bold"><FONT size=6>动机</FONT></SPAN>（Motivation）<BR>在计算科学中,魔法数(magic number)是历史最悠久的不良现象之一.<BR><BR>进行本项重构之前,你应该先寻找其他替换方案.你应该观察魔法数如何被使用,而后往往你会发现一种更好的使用方式.如果这个魔法数是个<STRONG>type code</STRONG>(型别码),请考虑使用<STRONG>Replace Type Code with Class</STRONG>(218);如果这个魔法数代表一个数组的长度,请在遍历该数组的时候,改用Array.length().<img src ="http://www.blogjava.net/ivanwan/aggbug/13065.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 13:54 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13065.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Magic Number with Symbolic Constant）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/15/13063.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Thu, 15 Sep 2005 05:46:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/15/13063.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13063.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/15/13063.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13063.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13063.html</trackback:ping><description><![CDATA[你有一个字面数值(literal number),带有特殊含义.<BR><BR><STRONG>创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量.<BR></STRONG><BR><FONT style="BACKGROUND-COLOR: #d3d3d3">double potentialEnergy(double mass, double height) {<BR>&nbsp;&nbsp;&nbsp;return mass * 9.81 * height;<BR>}<BR><BR><FONT style="BACKGROUND-COLOR: #ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <IMG height=41 alt=126.gif src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/126.gif" width=45 border=0></FONT><BR>double potentialEnergy(double mass, double height) {<BR>&nbsp;&nbsp;&nbsp;return mass * GRAVITATIONAL_CONSTANT * height;<BR>}<BR>static final double GRAVITATIONAL_CONSTANT = 9.81;&nbsp;</FONT><img src ="http://www.blogjava.net/ivanwan/aggbug/13063.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-15 13:46 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/15/13063.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Bidirectional Association to Unidirectional）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/14/13028.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Wed, 14 Sep 2005 09:29:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/14/13028.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13028.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/14/13028.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13028.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13028.html</trackback:ping><description><![CDATA[<SPAN style="FONT-WEIGHT: bold"><FONT size=5>范例</FONT></SPAN>（Examples）<BR>本例从<STRONG>Change Unidirectional association to Bidirectional</STRONG>(197)留下的代码开始进行,其中<STRONG>Customer</STRONG>和<STRONG>Order</STRONG>之间有双向关联:<BR><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">class Order...</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp;Customer getCustomer() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return _customer;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;void setCustomer(Custoemr arg) ...</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; if(_customer != null) _customer.friendOrders().remove(this);</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; _customer = arg;</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; if(_customer != null) _customer.friendOrders().add(this);</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;private Customer _customer;&nbsp;&nbsp;&nbsp;//译注:这是Order-<STRONG>to</STRONG>-Customer <STRONG>link</STRONG>也是本例的移除对象.<BR><BR><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">class Customer ...</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; void addOrder(Order arg) {</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; arg.setCustomer(this);</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;private Set _orders = new&nbsp;HashSet();<BR>&nbsp;&nbsp;&nbsp;//译注:以上是Customer-<STRONG>to</STRONG>-Order <STRONG>link<BR></STRONG>&nbsp;&nbsp;&nbsp;Set friendOrders() {<BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; return _orders;</SPAN><BR style="BACKGROUND-COLOR: rgb(211,211,211)"><SPAN style="BACKGROUND-COLOR: rgb(211,211,211)">&nbsp;&nbsp;&nbsp; }</SPAN></SPAN><BR></SPAN><BR>后来我发现,除非先有Customer对象,否则不会存在Order对象.因此我想将[从Order到Customer的连接]移除掉.<BR><BR>对于本项重构来说,最困难的就是检查可行性.如果我知道本项重构是安全的,那么重构手法自身十分简单.问题在于是否有任何代码倚赖_customer值域的存在.如果确实有,那么在删除这个值域之后,我必须提供替代品.<BR><BR>首先,我需要研究所有读取这个值域的函数,以及所有使用这些函数的函数.我能找到另一条途径来供应Customer对象吗----这通常意味将Customer对象作为引数(argument)传递给其用户(某函数).下面是一个简化例子:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Order...<BR>&nbsp;&nbsp;&nbsp;double getDiscountedPrice() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return getGrossPrice() * (1 - _customer.getDiscount());<BR>&nbsp;&nbsp;&nbsp;}</FONT><BR>改变为:<BR>class Order...<BR>&nbsp;&nbsp;&nbsp;<FONT style="BACKGROUND-COLOR: #d3d3d3">double getDiscountedPrice(<STRONG>Customer customer</STRONG>) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return getGrossPrice() * (1 - <STRONG>customer</STRONG>.getDiscount());<BR>&nbsp;&nbsp;&nbsp;}</FONT><BR>如果待改函数是被Customer对象调用的,那么这样的修改方案特别容易实施,因为Customer对象将自己作为引数(argument)传给函数很是容易.所以下列代码:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Customer...<BR>&nbsp;&nbsp;&nbsp;double getPriceFor(Order order) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Assert.isTrue(_orders.contains(order));&nbsp;&nbsp;&nbsp;//see </FONT><FONT style="BACKGROUND-COLOR: #d3d3d3"><STRONG>Introduce Assertion(267)<BR></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return order.getDiscountedPrice();</FONT><BR>变成了:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">class Customer...<BR>&nbsp;&nbsp;&nbsp;double getPriceFor(Order order) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Assert.isTrue(_orders.contains(order));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return order.getDiscountedPrice(<STRONG>this</STRONG>);<BR></FONT>另一个作法就是修改取值函数(getter),使其在不使用_customer值域的前提下返回一个<STRONG>Customer</STRONG>对象.如果这行得通,我就可以使用<STRONG>Substitute Algorithm</STRONG>(139)修改Order.getCustomer()函数算法.我有可能这样修改代码:<BR><FONT style="BACKGROUND-COLOR: #d3d3d3">Customer getCustomer() {<BR>&nbsp;&nbsp;&nbsp;Iterator iter = Customer.getInstance().iterator();<BR>&nbsp;&nbsp;&nbsp;while(iter.hasNext()) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Customer each = (Customer)iter.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(each.containsOrder(this) return each;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;return null;<BR>}</FONT><BR>这段代码比较慢,不过确实可行.而且,在数据库环境下,如果我需要使用数据库查询语句,这段代码对系统性能的影响可能并不显著.如果,Order class中有些函数使用_customer值域,我可以实施<STRONG>Self Encapsulate Field</STRONG>(171)令它们转而改用上述的getCustomer()函数.<BR><BR>如果我要保留上述的取值函数(getter),那么<STRONG>Order</STRONG>和<STRONG>Customer</STRONG>的关联从接口上看虽然仍然是双向,但实现上已经是单向关系了.虽然我移除了反向指针,但两个classes彼此之间的依存关系(inter-dependencies)仍然存在.<BR><BR>如果我要替换取值函数(getter),那么我就专注地替换它,其他部分留待以后处理.我会逐一修改取值函数的调用者.让它们通过其他来源取得Customer对象.每次修改后都编译并测试.实际工作中这一过程往往相当快.如果这个过程让我觉得很棘手很复杂,我会放弃本项重构.<BR><BR>一旦我消除了_customer值域的所有读取点,我就可以着手处理[对此值域进行赋值动作]的函数了.很简单,只要把这些赋值动作全部移除,再把值域一并删除,就行了.由于已经没有任何代码需要这个值域,所以删掉它并不会带来任何影响.<img src ="http://www.blogjava.net/ivanwan/aggbug/13028.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-14 17:29 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/14/13028.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Bidirectional Association to Unidirectional）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/14/13021.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Wed, 14 Sep 2005 07:11:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/14/13021.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/13021.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/14/13021.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/13021.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/13021.html</trackback:ping><description><![CDATA[<UL>
<LI><SPAN style="FONT-WEIGHT: bold"><FONT size=6>作法</FONT></SPAN>（Mechanics)<BR></LI>
<UL>
<LI>找出[你想去除的指针]的保存值域,检查它的每一个用户,判断是否可以去除该指针.</LI>
<UL>
<UL>
<LI>==&gt;不但要检查[直接读取点],也要检查[直接读取点]的调用函数.</LI>
<LI>==&gt;考虑有无可能不通过指针函数取得[被引用对象](referred object).如果有可能,你就可以对取值函数(getter)使用<STRONG>Substitute Algorithm</STRONG>(139).从而让客户在没有指针的情况下也可以使用该取值函数.</LI>
<LI>==&gt;对于使用该值域的所有函数,考虑将[被引用对象](referred object)作为引数(argument)传进去.</LI></UL></UL>
<LI>如果客户使用了取值函数(getter),先运用Self Encapsulate Field(171)将[待除值域]自我封装起来,然后使用Subsitute Algorithm(139)对付取值函数,令它不再使用该(待除)值域.然后编译,测试.</LI>
<LI>如果客户并使用取值函数(getter),那就直接修改[待除值域]的所有被引用点:改以其他途径获得该值域所保存的对象.每次修改后,编译并测试.</LI>
<LI>如果已经没有任何函数使用该(待除)值域,移除所有[对该值域的更新逻辑],然后移除该值域.</LI>
<UL>
<UL>
<LI>==&gt;如果有许多地方对此值域赋值,先运用<STRONG>Self Encapsulate Field</STRONG>(171)使这些地点改用同一个设值函数(setter).编译,测试.而后将这个设值函数的本体清空.再编译,再测试.如果这些都可行,就可以将此值域和其设值函数,连同对设值函数的所有调用,全部移除.</LI></UL></UL>
<LI>编译,测试.</LI></UL></UL><img src ="http://www.blogjava.net/ivanwan/aggbug/13021.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-14 15:11 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/14/13021.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Bidirectional Association to Unidirectional）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/14/12998.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Wed, 14 Sep 2005 03:20:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/14/12998.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12998.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/14/12998.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12998.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12998.html</trackback:ping><description><![CDATA[<SPAN style="FONT-WEIGHT: bold"><FONT size=6>动机</FONT></SPAN>（Motivation）<BR>双向关联(bidirectional associations)很有用,但你也必须为它付出代价,那就是[维护双向链接,确保对象被正确创建和删除]而增加的复杂度.而且,由于很多程序员并不习惯使用双向关联,它往往成为错误之源.<BR><BR>大量的双向连接(two-way links)也很容易引发[僵尸对象]:某个对象本来已经该死亡了,却仍然保留在系统中,因为对它的各项引用还没有完全清除.<BR><BR>此外,双向关联也迫使两个classes之间有了相依性.对其中任一个class的任何修改,都可能引发另一个class的变化.如果这两个classes处在不同的package中,这种相依性就是packages之间的相依.过多的依存性(inter-dependencies)会造成就紧耦合(highly coupled)系统,使得任何一点小小改动都可能造成许多无法预知的后果.<BR><BR>只有在你需要双向关联的时候,才应该使用它.如果你发现双向关联不再有存在价值,就应该去掉其中不必要的一条关联.<img src ="http://www.blogjava.net/ivanwan/aggbug/12998.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-14 11:20 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/14/12998.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Bidirectional  Association to Unidirectional）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/14/12992.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Wed, 14 Sep 2005 03:08:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/14/12992.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12992.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/14/12992.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12992.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12992.html</trackback:ping><description><![CDATA[两个classes之间有双向关联,但其中一个class如今不再需要另一个class的特性.<BR><BR><STRONG>去除不必要的关联(association).<BR><BR><IMG height=251 alt="Change Bidirectional  Association to Unidirectional.jpg" src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/Change%20Bidirectional%20%20Association%20to%20Unidirectional.jpg" width=401 border=0></STRONG><img src ="http://www.blogjava.net/ivanwan/aggbug/12992.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-14 11:08 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/14/12992.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Unidirectional Association to Bidirectional）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/13/12891.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Tue, 13 Sep 2005 07:55:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/13/12891.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12891.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/13/12891.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12891.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12891.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="5">范例</font></span>（Examples）<br>
下面是一段简单程序，其中有两个classes：表示[定单]的<span style="font-weight: bold;">Order</span>和表示[客户]的<span style="font-weight: bold;">Customer</span>。<span style="font-weight: bold;">Order</span>引用了<span style="font-weight: bold;">Customer</span>，<span style="font-weight: bold;">Customer</span>则并没有引用<span style="font-weight: bold;">Order</span>：<br>
<span style="background-color: rgb(211, 211, 211);">class Order...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; Customer getCustomer() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return _customer;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void setCustomer(Customer arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _Customer = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; Customer _customer;&nbsp;&nbsp;  //这是一个“Order”to “Customer”的连接</span><br>
<br>
首先，我要为<span style="font-weight: bold;">Customer</span>添加一个值域。由于一个客户可以拥有多份定单，所以这个新增值域应该是个群集（collection）。我不希望同一份定单在同一个群集中出现一次以上，所以这里适合使用set：<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">class Customer {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; private Set _orders = new HashSet();</span><br>
现在，我需要决定由哪一个class负责控制关联性（association）。我比较喜欢让单一class来操控，因为这样我就可以将所有[关联处理逻辑]集中安置于一地。我将按照下列步骤做出这一决定：<br>
<ol>
  <li>如果两者都是<span style="font-weight: bold;">reference</span> objects，而其间的关联是[一对多]关系，那么就由[拥有单一reference]的那一方承担[控制者]角色。以本例而言，如果一个客户可拥有多份定单，那么就由<span style="font-weight: bold;">Order</span> class(定单)来控制关联性。</li><li>如果某个对象是另一个对象的组成（component），那么由后者负责控制关联性。</li>
  <li>如果两者都是<span style="font-weight: bold;">reference</span> objects，而其间的关联是[多对多]关系，那么随便其中哪个对象来控制关联性，都无所谓。</li>

</ol>本例之中由于Order负责控制关联性，所以我必须为Customer添加一个赋值函数，让Order可以直接访问_orders（订单）集群。<span style="background-color: rgb(211, 211, 211);">Order</span>的修改函数（modifier）将使用这个辅助函数对指针两端对象进行同步控制。我将这个赋值函数命名为friendOrders（），表示这个函数只能在这种特殊情况下使用。此外，如果<span style="font-weight: bold;">Order</span>和<span style="font-weight: bold;">Customer</span>位在同一个<span style="font-weight: bold;">package</span>内，我还会将friendOrders（）声明为[package&nbsp; 可见度]，使其可见程度降至最低。<br>
<br>
但如果这两个classes不在同一个package内，我就只好把friendOrders（）声明为<span style="font-weight: bold;">public</span>了。<br>
<span style="background-color: rgb(211, 211, 211);">class Customer...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; Set friendOrders() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return _orders;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br>
现在。我要改变修改函数（modifier），令它同时更新反向指针：<br>
<br>
<span style="background-color: rgb(211, 211, 211);">class Order...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void setCustomer(Custoemr arg) ...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  if(_customer != null) _customer.friendOrders().remove(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; _customer = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  if(_customer != null) _customer.friendOrders().add(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<br>
classes之间的关联性是各式各样的，因此修改函数（modifier）的代码也会随之有所差异。如果_customer的值不可能是null，我可
以拿掉上述的第一个null检查，但仍然需要检查引数（argument）是否是null。不过，基本形式总是相同的：先让对方删除[指向你]的指针，再
将你的指针指向一个新对象，最后让那个新对象把它的指针指向你。<br>
<br>
如果你希望在<span style="font-weight: bold;">Customer</span>中也能修改连接（link），就让它调用控制函数：<br>
<br>
<span style="background-color: rgb(211, 211, 211);">class Customer ...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void addOrder(Order arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; arg.setCustomer(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br>
如果一份订单也可以对应多个客户，那么你所面临的就是一个[多对多]情况，重构后的函数可能是下面这样：<br>
<span style="background-color: rgb(211, 211, 211);">class Order ...&nbsp;&nbsp;  //controlling methods</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void addCustomer(Customer arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  arg.friendOrders().add(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _customers.add(arg);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void removeCustomer(Customer arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  arg.friendOrders().remove(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _customers.remove(arg);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">class Customer ...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void addOrder(Order arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  arg.addCustomer(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; void removeOrder(Order arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  arg.removeCustomer(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12891.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-13 15:55 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/13/12891.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Unidirectional Association to Bidirectional）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/13/12881.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Tue, 13 Sep 2005 07:00:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/13/12881.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12881.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/13/12881.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12881.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12881.html</trackback:ping><description><![CDATA[<UL>
<LI><SPAN style="FONT-WEIGHT: bold"><FONT size=6>作法</FONT></SPAN>（Mechanics)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</LI>
<UL>
<UL>
<LI>在referred class中增加一个值域,用以保存[反向指针].</LI>
<LI>决定由哪个class(引用端或被引用端)控制关联性(association).</LI>
<LI>在[被控制]建立一个辅助函数,其命名应该清楚指出它的有限用途.</LI>
<LI>如果既有的修改函数(modifier)在[控制端],让它负责更新反向指针.</LI>
<LI>如果既有的修改函数(modifier)在[控制端]</LI></UL></UL></UL><img src ="http://www.blogjava.net/ivanwan/aggbug/12881.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-13 15:00 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/13/12881.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据(Change Unidirectional Association to Bidirectional)（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/13/12845.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Tue, 13 Sep 2005 02:31:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/13/12845.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12845.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/13/12845.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12845.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12845.html</trackback:ping><description><![CDATA[开发初期,你可能会在两个classes之间建立一条单向连接,使其中一个class可以引用另一个class.随着时间推移,你可能发现referred class需要得到其引用者(某个object)以便进行某些处理.也就是说它需要一个反向指针.<BR><BR>[反向指针]手法有点棘手,所以在你能够自在运用它之前,应该有相应的测试.通常我不花心思去测试访问函数(accessors),因为普通访问函数的风险没有高到需要测试的地步,但本重构要求测试访问函数,所以它是极少数需要添加测试的重构手法之一.<BR><BR>本重构运用反向指针(back pointer)实现双向关联(bidirectionality).其他技术(例如连接对象,link objects)需要其他重构手法.<img src ="http://www.blogjava.net/ivanwan/aggbug/12845.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-13 10:31 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/13/12845.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Unidirectional Association to Bidirectional）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/12/12746.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 12 Sep 2005 05:53:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/12/12746.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12746.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/12/12746.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12746.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12746.html</trackback:ping><description><![CDATA[两个classes都需要使用对方特性，但其间只有一条单向连接（one-way link）。<br>
<br>
<span style="font-weight: bold;">添加一个反向，并使修改函数（modifiers）能够同时更新两条连接</span>。（注译：这里的指针等同于句柄（handle），修改函数（modifier）指的是改变双方关系者）<br>
<br>
<img src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/Change%20Unidirectional%20Association%20to%20Bidirectional.png" alt="Change Unidirectional Association to Bidirectional.png" border="0" height="271" width="403"><br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12746.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-12 13:53 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/12/12746.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Duplicate Observed Data）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/07/12308.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Wed, 07 Sep 2005 04:19:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/07/12308.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12308.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/07/12308.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12308.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12308.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">作法</font></span>（Mechanics)<br>
<ul>
  <ul>
    <li>修改<span style="font-weight: bold;">presentation class</span>，使其成为<span style="font-weight: bold;">domain class</span>的<span style="font-weight: bold;">Observer[GoF]</span>。</li>
    <ul>
      <ul>
        <li>==》如果尚未有domain class，就建立一个。</li>
        <li>==》如果没有[从presentation class到domain class]的关联性（link），就将domain class保存于presentation class的一个值域中。</li>
      </ul>
    </ul>
    <li>针对<span style="font-weight: bold;">GUI</span> class内的domain data，使用<span style="font-weight: bold;">Self Encapsulate Field</span>（171）。</li>
    <li>编译，测试。</li>
    <li>在事件处理函数（event handler）中加上对设值函数（setter）的调用，以[直接访问方式]更新GUI组件。</li>
    <ul>
      <ul>
        <li>==》在事件处理函数中放一个设值函数（setter），利用它将GUI组件更新为domain data的当前值。当然这其实没有必要，你只不过是拿它的值设定它自己。但是这样使用setter，便是允许其中的任何动作得以于日后被执行起来，这是这一步骤的意义所在。</li>
        <li>==》进行这个改变时，对于组件，不要使用取值函数（getter），应该采取[直接取用]方式，因为稍后我们将修改取值函数（getter），使其从domain object（而非GUI组件）取值。设值函数（setter）也将遭受类似修改。</li>
        <li>==》确保测试代码能够触发新添加的事件处理（event handling）机制。</li>
      </ul>
    </ul>
    <li>编译，测试。</li>
    <li>在domain class中定义数据及其相关访问函数（accessors）。</li>
    <ul>
      <ul>
        <li>==》确保domain class中的设值函数（setter）能够触发<span style="font-weight: bold;">Observer</span>模式的通报机制（notify mechanism）。</li>
        <li>==》对于被观察（被监视）的数据，在domain class中使用[与presentation class所用的相同型别]（通常是字符串）来保存。后续重构中你可以自由改变这个数据型别。</li>
      </ul>
    </ul>
    <li>修改presentation class中的访问函数（accessors），将它们的操作对象改为domain object（而非GUI组件）。</li>
    <li>修改observer的update（），使其从相应的domain object中将所需数据拷贝给GUI组件。</li>
    <li>编译，测试。<br>
    </li>
  </ul>
</ul>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12308.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-07 12:19 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/07/12308.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Duplicate Observed Data）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/06/12191.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Tue, 06 Sep 2005 03:07:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/06/12191.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12191.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/06/12191.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12191.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12191.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="5">范例</font></span>（Examples）<br>我们的范例其行为非常简单:当用户修改文本框中的数值,另两个文本框就会自动更新.如果你修改Start或End,length就会自动成为两者计算所得的长度;如果你修改length,End就会随之变动.<br><br>一开始,所有函数都放在IntervalWindow class中.所有文本框都能够响应[失去键盘焦点]（loss of focus）这一事件。<br>
<span style="background-color: rgb(211, 211, 211);">public class IntervalWindow extends Frame...</span><br style="background-color: rgb(211, 211, 211);"><span style="background-color: rgb(211, 211, 211);">
    java.awt.TextField _startField;</span><br style="background-color: rgb(211, 211, 211);"><span style="background-color: rgb(211, 211, 211);">
    java.awt.TextField _endField;</span><br style="background-color: rgb(211, 211, 211);"><span style="background-color: rgb(211, 211, 211);">
    java.awt.TextField _lengthField;</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);"><span style="background-color: rgb(211, 211, 211);">
    class SymFocus extends java.awt.event.FocusAdapter</span><br style="background-color: rgb(211, 211, 211);"><span style="background-color: rgb(211, 211, 211);">
    {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        public void focusLost(java.awt.event.FocusEvent event)</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">            Object object = event.getSource();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    </span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">            if(object == _startField)</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">               StartField_FocusLost(event);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">            else if(object = _endField)</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">                EndField_FocusLost(event);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">            else if(object = _lengthField)</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">                LengthField_FocusLost(event);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
<br>
当<span style="font-weight: bold;">Start</span>文本框失去焦点，事件监听器调用StartField_FocusLost()。另两个文本框的处理也类似。事件处理函数大致如下：<br>
<span style="background-color: rgb(211, 211, 211);">void StartField_FocusLost(java.awt.event.FocusEvent event) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    if(isNotInteger(_startField.getText()))</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        _startField.setText("0");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    calculateLength();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">void EndField_FocusLost(java.awt.event.FocusEvent event) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    if(isNotInteger(_endField.getText()))</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        _endField.setText("0");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    calculateLength();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">void LengthField_FocusLost(java.awt.event.FocusEvent event) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    if(isNotInteger(_lengthField.getText()))</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        _lengthField.setText("0");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    calculateLength();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
<br>
如果文本框的字符串无法转换为一个整数，那么该文本框的内容将变成0。而后，调用相关计算函数：<br>
<span style="background-color: rgb(211, 211, 211);">void calculateLength() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    try {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        int start = Integer.parseInt(_startField.getText());</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        int end = Integer.parseInt(_endField.getText());</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        int length = end - start;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        _lengthField.setText(String.valueOf(length));</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    } catch(NumberFormatException e) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        throw new RuntimeException("Unexpected Number Format Error");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">void calculateEnd() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    try {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
        int start = Integer.parseInt(_startField.getText());</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
        int end = Integer.parseInt(_endField.getText());</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
        int end = start + length;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
        _endField.setText(String.valueOf(end));</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
    } catch(NumberFormatException e) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
        throw new RuntimeException("Unexpected Number Format Error");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
    }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">
}</span><br>
我的任务就是非视觉性的计算逻辑从GUI中分离出来。基本上这就意味将calculateLength（）和calculateEnd（）移到一个独立的domain class去。为了这一目的，我需要能够在不引用窗口类的前提取用<span style="font-weight: bold;">Start</span>、<span style="font-weight: bold;">End</span>和<span style="font-weight: bold;">length</span>三个文本框的值。唯一办法就是将这些数据复制到domain class中，并保持与GUI class数据同步。这就是<span style="font-weight: bold;">Duplicate Observed Data</span>（189）的任务。<br>
<br>
截至目前我还没有一个domain class，所以我着手建立一个：<br>
<span style="background-color: rgb(211, 211, 211);">class Interval extends Observable {}</span><br>
<span style="font-weight: bold;"><br>
IntervalWindow</span> class需要与此崭新的domain class建立一个关联：<br>
<span style="background-color: rgb(211, 211, 211);">private Interval _subject;</span><br>
<br>
然后，我需要合理地初始化_subject值域，并把I<span style="font-weight: bold;">ntervalWindow</span> class变成<span style="font-weight: bold;">Interval </span>class的一个<span style="font-weight: bold;">Observer</span>。这很简单，只需把下列代码放进IntervalWindow构造函数中就可以了：<br>
<span style="background-color: rgb(211, 211, 211);">_subject = new Interval();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">_subject.addObserver(this);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">update(_subject, null);</span><br style="background-color: rgb(211, 211, 211);">
<br>
我喜欢把这段代码放在整个建构过程的最后。其中对update（）的调用可以确保：当我把数据复制到<span style="font-weight: bold;">domain</span> class后，GUI将根据<span style="font-weight: bold;">domain</span> class进行初始化。update（）是在java.util.observer接口中声明的，因此我必须让<span style="font-weight: bold;">IntervalWindow</span> class实现这一接口：<br>
<span style="background-color: rgb(211, 211, 211);">public class IntervalWindow extends Frame implements Observer</span><br>
然后我还需要为IntervalWindow class建立一个update（）。此刻我先令它为空：<br>
<span style="background-color: rgb(211, 211, 211);">public void update(Observable observed, Object arg)  {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
现在我可以编译并测试了。到目前为止我还没有作出任何真正的修改。呵呵，小心驶得万年船。<br>
<br>
接下来我把注意力转移到文本框。一如往常我每次只改动一点点。为了卖弄一下我的英语能力，我从<span style="font-weight: bold;">End</span>文本框开始。第一件要做的事就是实施<span style="font-weight: bold;">Self Encapsulate Field</span>（171）。文本框的更新是通过getText（）和setText（）两函数实现的，因此我所建立的访问函数（accessors）需要调用这两个函数：<br>
<br>
<span style="background-color: rgb(211, 211, 211);">String getEnd() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    return _endField.getText();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">void setEnd(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    _endField.setText(arg);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
然后，找出_endField的所有引用点，将它们替换为适当的访问函数：<br>
<span style="background-color: rgb(211, 211, 211);">void calculateLength() {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">    try {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        int start = Integer.parseInt(_startField.getText());</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        int end = Integer.parseInt(<span style="font-weight: bold;">getEnd()</span>);</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        int length = end - start;</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        _lengthField.setText(String.valueOf(length));</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">    } catch(NumberFormatException e) {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        throw new RuntimeException("Unexpected Number Format Error");</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">    }</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">void calculateEnd() {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">    try {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
        int start = Integer.parseInt(_startField.getText());</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
        int end = Integer.parseInt(_endField.getText());</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
        int end = start + length;</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
       <span style="font-weight: bold;">setEnd</span>(String.valueOf(end));</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
    } catch(NumberFormatException e) {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
        throw new RuntimeException("Unexpected Number Format Error");</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
    }</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">
}</span><br>
<span style="background-color: rgb(211, 211, 211);">void EndField_FocusLost(java.awt.event.FocusEvent event) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    if(isNotInteger(<span style="font-weight: bold;">getEnd</span>()))</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        <span style="font-weight: bold;">setEnd</span>("0");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    calculateLength();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
<br>这是<span style="font-weight: bold;">Self Encapsulate Field</span>（171）的标准过程。然后当你处理GUI class时，情况还更复杂些：用户可以直接（通过GUI）修改文本框内容，不必调用setEnd()。因此我需要在GUI class的事件处理函数中加上对setEnd（）的调用。这个动作把<span style="font-weight: bold;">End</span>文本框设定为其当前值。当然，这没带来什么影响，但是通过这样的方式，我们可以确保用户的输入的确是通过设值函数（<span style="font-weight: bold;">setter</span>）进行的：<br>
<span style="background-color: rgb(211, 211, 211);">void EndField_FocusLost(java.awt.event.FocusEvent event) {</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);"><span style="font-weight: bold;">    setEnd(_endField.getText());</span><br>
    if(isNotInteger(getEnd()))</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">        setEnd("0");</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">    calculateLength();</span><br style="background-color: rgb(211, 211, 211);">

<span style="background-color: rgb(211, 211, 211);">}<br>
</span><br>
上述调用动作中，我并没有使用上一页的getEnd()取得<span style="font-weight: bold;">End</span>文
本框当前内容，而是直接取用该文本框。之所以这样做是因为，随后的重构将使上一页的getEnd()从domain
object（而非文本框）身上取值。那时如果这里用的是getEnd（）函数，每当用户修改文本框内容，这里就会将文本框又改回原值。所以我必须使用
[直接访问文本框]的方式获得当前值。现在我可以编译并测试值域封装后的行为了。<br>
<br>
现在，在domain class中加入_end值域：<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">class Interval...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    private String _end = "0";</span><br>
在这里，我给它的初始值和GUI class给它的初值是一样的。然后我再加入取值/设值函数（getter/setter）：<br>
<span style="background-color: rgb(211, 211, 211);">class Interval...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    String getEnd() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        return _end;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    void setEnd(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        _end = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        setChanged();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        notifyObservers();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    }</span><br>
<br>
由于使用了<span style="font-weight: bold;">Observer</span>模式，我必须在设值函数（setter）
中加上[发出通告]动作（即所谓notification
code）。我把_end声明为一个字符串，而不是一个看似更合理的整数，这是因为我希望将修改量减至最少。将来成功复制数据完毕后，我可以自由自在地于
domain class内部把_end声明为整数。<br>
<br>
现在，我可以再编译并测试一次。我希望通过所有这些预备工作，将下面这个较为棘手的重构步骤的风险降至最低。<br>
<br>
首先，修改IntervalWindow class的访问函数，令它们改用Interval对象：<br>
<span style="background-color: rgb(211, 211, 211);">class IntervalWindow...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    String getEnd() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        return <span style="font-weight: bold;">_subject</span>.getEnd();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    void setEnd(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">        <span style="font-weight: bold;">_subject</span>.setEnd(arg);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">    }</span><br>
同时也修改update（）函数，确保<span style="font-weight: bold;">GUI</span>对<span style="font-weight: bold;">Interval</span>对象发来的通告做出响应：<br>
class IntervalWindow...<br>
    public void update(Observable observed, Object arg) {<br>
      <span style="font-weight: bold;">  _endField.setText(_subject.getEnd());</span><br>
    }<br>
这是另一个需要[直接取用文本框]的地点。如果我调用的是设值函数（setter），程序将陷入无限递归调用（这是因为IntervalWindow的设
值函数setEnd（）调用了Interval。setEnd（），一如稍早行所示：而Interval.setEnd()又调用
notifyObservers（），导致IntervalWindow.update()又被调用）。<br>
<br>
现在，我可以编译并测试，数据都恰如其分地被复制了。<br>
<br>
另两个文本框也如法炮制。完成之后，我可以使用Move Method（142）将calculateEnd()和calculateLength()搬到<span style="font-weight: bold;">Interval</span> class。这么一来，我就拥有一个[包容所有domain behavior和domain data]并与<span style="font-weight: bold;">GUI</span> code分离的domain class了。<br>
<br>
如果上述工作都完成了，我就会考虑彻底摆脱这个GUI class。如果GUI class是个较为老旧的AWT
class，我会考虑将它换成一个比较好看的Swing class，而且后者的坐标定位能力也比较强。我可以在domain
class之上建立一个Swing GUI。这样，只要我高兴，随时可以去掉老旧的GUI class。<br>
<br>
<font size="5"><span style="font-weight: bold;">使用事件监听器（Event Listeners）</span></font><br>
<br>
如果你使用事件监听器（event listener）而不是<span style="font-weight: bold;">Observer/Observable</span>模式，仍然可以实施<span style="font-weight: bold;">Duplicate Observed Data</span>（189）。这种情况下，你需要在domain model中建立一个<span style="font-weight: bold;">listener</span> class和一个<span style="font-weight: bold;">event</span>
class。然后，你需要对domain
object注册listeners，就像前例对observable对象注册observers一样。每当domain
object发生变化（类似上例的update（）函数被调用），就向listeners发送一个事件（event）。<span style="font-weight: bold;">IntervalWindow</span> class可以利用一个inner class（内嵌类）来实现监听器接口（listener interface），并在适当时候调用适当的update（）函数。<br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12191.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-06 11:07 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/06/12191.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Duplicate Observed Data）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12052.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 09:04:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12052.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12052.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12052.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12052.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12052.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">动机</font></span>（Motivation）<br>
一个分层良好的系统，应该将处理用户界面（UI）和处理业务逻辑（business
logic）的代码分开。之所以这样做，原因有以下几点：（1）你可能需要使用数个不同的用户界面来表现相同的业务逻辑；如果同时承担两种责任，用户界面
会变得过分复杂；（2）与GUI隔离之后，domain objects的维护和演化都会更容易；你甚至可以让不同的开发者负责不同部分的开发。<br>
<br>
如果你遇到的代码是以双层（two-tiered）方式开发，业务逻辑被内嵌于用户界面（UI)之中，你就有必要将行为分离出来。其中的主要工作就是函数的分离和搬移。但数据就不同了：你不能仅仅只是移动数据，你必须将它复制到新建部位中，并提供相应的同步机制。<br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12052.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 17:04 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12052.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Duplicate Observed Data）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12051.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 08:50:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12051.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12051.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12051.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12051.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12051.html</trackback:ping><description><![CDATA[注：所谓presentation class，用以处理[数据表现形式]；所谓domain class，用以处理业务逻辑。<br>
<br>
你有一些domain data置身于GUI控件中，而domain method需要访问之。<br>
<br>
<span style="font-weight: bold;">将该笔数据拷贝一个domain object中。建立一个Observer模式，用以对domain object和GUI object内的重复数据进行同步控制（sync.）。<br>
<br>
<img src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/Duplicate%20Observed%20Data.png" alt="Duplicate Observed Data.png" border="0" height="433" width="797"><br>
</span><img src ="http://www.blogjava.net/ivanwan/aggbug/12051.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 16:50 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12051.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Array with Object）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12039.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 07:09:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12039.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12039.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12039.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12039.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12039.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="5">范例</font></span>（Examples）<br>
我们的范例从一个数组开始，其中有三个元素，分别保存一支球队的名称、获胜场次和失利场次。这个数组的声明可能像这样：<br>
<span style="background-color: rgb(211, 211, 211);">String[] row = new String[3];</span><br>
客户端代码可能像这样：<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row[0] = "Livepool";</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row[1] = "15";</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">String name = row[0];</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">int wins = Integer.parseInt(row[1]);</span><br>
<br>
为了将数组变成对象，我首先建立一个对应的class：<br>
<span style="background-color: rgb(211, 211, 211);">class Performance{}</span><br>
然后为它声明一个public值域，用以保存原先数组。（我知道public值域十恶不赦，请放心，稍后我便让它改邪归正。）<br>
<span style="background-color: rgb(211, 211, 211);">public String[] _data = new String[3];</span><br>
现在，我要找到创建和访问数组的地方。在创建地点，我将它替换为下列代码：<br>
<span style="background-color: rgb(211, 211, 211);"><span style="font-weight: bold;">Performance</span> row = new <span style="font-weight: bold;">Performance</span>();</span><br>
对于数组使用地点，我将它替换为以下代码：<br>
<span style="background-color: rgb(211, 211, 211);">row.</span><span style="font-weight: bold; background-color: rgb(211, 211, 211);">_data</span><span style="background-color: rgb(211, 211, 211);">[0] = "Liverpool";</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row.</span><span style="font-weight: bold; background-color: rgb(211, 211, 211);">_data</span><span style="background-color: rgb(211, 211, 211);">[1] = "15";</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">String name = row.</span><span style="font-weight: bold; background-color: rgb(211, 211, 211);">_data</span><span style="background-color: rgb(211, 211, 211);">[0];</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">int wins = Integer.parseInt(row.</span><span style="font-weight: bold; background-color: rgb(211, 211, 211);">_data</span><span style="background-color: rgb(211, 211, 211);">[1]);</span><br>
<br>
然后我要逐一为数组元素加上有意义的取值/设值函数（getters/setters）。首先从[球队名称]开始：<br>
<span style="background-color: rgb(211, 211, 211);">class Performance...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public String getName() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return _data[0];</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public void setName(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _data[0] = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br>
然后修改row对象的用户，让他们改用[取值/设值函数]来访问球队名称：<br>
<span style="background-color: rgb(211, 211, 211);">row.<span style="font-weight: bold;">setName("Liverpool");</span></span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row._data[1] = "15";</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">String name = row.<span style="font-weight: bold;">getName();</span></span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">int wins = Integer.parseInt(row._data[1]);</span><br>
第二个元素也如法炮制。为了简单起见，我还可以把数据型别的转换也封装起来：<br>
<span style="background-color: rgb(211, 211, 211);">class Performance...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public int getWins() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return Integer.parseInt(_data[1]);</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public void setWins(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _data[1] = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">client code...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; row.setName("Liverpool");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; row.<span style="font-weight: bold;">setWins("15");</span></span><br style="background-color: rgb(211, 211, 211); font-weight: bold;">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; String name = row.getName();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; int wins = row.<span style="font-weight: bold;">getWins();</span></span><br>
处理完所有元素之后，我就可以将保存该数组的值域声明为private了。<br>
<span style="background-color: rgb(211, 211, 211);">private String[] _data = new String[3];</span><br>
现在，本次重构最重要的部分（接口修改）已经完成。但是[将对象内的数组替换掉]的过程也同样重要。我可以针对每个数组元素，在class内建立一个型别相当的值域，然后修改该数组元素的访问函数，令它直接访问新建值域，从而完全摆脱对数组元素的依赖。<br>
<span style="background-color: rgb(211, 211, 211);">class Performance...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public String getName() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return <span style="font-weight: bold;">_name;</span></span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public void setName(String arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  <span style="font-weight: bold;">_name</span> = arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; private String _name;</span><br>
<br>
对数组中的每一个元素都如法炮制。全部处理完毕后，我就可以将数组从我的<span style="font-weight: bold;">Performance</span> class中删掉了。<br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12039.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 15:09 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12039.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Array with Object）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12026.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 05:49:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12026.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12026.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12026.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12026.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12026.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">作法</font></span>（Mechanics)<br>
<ul>
  <ul>
    <li>新建一个class表示数组所示信息，并在该class中以一个public值域保存原先的数组。</li>
    <li>修改数组的所有用户，让它们改用新建的class实体。</li>
    <li>编译，测试。</li>
    <li>逐一为数组元素添加取值/设值函数（getters/setters）。根据元素的用途，为这些访问函数命名。修改客户端代码，让它们通过访问函数取用数组内的元素。每次修改后，编译并测试。</li>
    <li>当所有[对数组的直接访问]都被取代为[对访问函数的调用]后，将class之中保存该数组的值域声明为private。</li>
    <li>编译。</li>
    <li>对于数组内的每一个元素，在新class中创建一个型别相当的值域；修改该元素的访问函数，令它改用上述的新建值域。</li>
    <li>每修改一个元素，编译并测试。</li>
    <li>数组的所有元素都在对应的class内有了相应值域之后，删除该数组。<br>
    </li>
  </ul>
</ul>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12026.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 13:49 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12026.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Array with Object）（2）--动机</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12022.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 05:30:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12022.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12022.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12022.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12022.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12022.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">动机</font></span>（Motivation）<br>
数组（array）是一种常见的用以组织数据的结构体。不过，它们应该只用于[以某种顺序容纳一组相似对象]。有时侯你会发现，一个数组容纳了数种不同对
象，这会给array用户带来麻烦，因为他们很难记住像[数组的第一个元素是人名]这样的约定。对象就不同了，你可以运用值域名称和函数名称来传达这样的
信息，因此你不需死记它，也无需依赖注释。而且如果使用对象，你还可以将信息封装起来，并使用<span style="font-weight: bold;">Move Method</span>（142）为它加上相关行为。<br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12022.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 13:30 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12022.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Replace Array with Object）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12021.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 05:22:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12021.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12021.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12021.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12021.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12021.html</trackback:ping><description><![CDATA[你有一个数组（array），其中的元素各自代表不同的东西。<br>
<br>
<span style="font-weight: bold;">以对象替换数组。对于数组中的每个元素，以一个值域表示之。</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">String[] row = new String[3];</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row[0] = "Livepool";</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row[1] = "15";<br>
<span style="background-color: rgb(255, 255, 255);">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;
&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <img src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/126.gif" alt="126.gif" border="0" height="41" width="45"><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">Performance row = new Performance();</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row.setName("Livepool");</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">row.setWins("15");</span><br>
</span></span><span style="font-weight: bold;"></span><img src ="http://www.blogjava.net/ivanwan/aggbug/12021.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 13:22 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12021.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Reference to Value）（4）--范例</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12008.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 03:04:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12008.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12008.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12008.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12008.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12008.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="5">范例</font></span>（Examples）<br>
让我们从一个表示[货币种类]的Currency class开始：<br>
<span style="background-color: rgb(211, 211, 211);">class Currency...</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; private String _code;</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; public String getCode() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  return _code;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br style="background-color: rgb(211, 211, 211);">
<br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; private Currency(String code) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;  _code = code;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; }</span><br>
这个class所做的就是保存并返回一个货币种类代码。它是一个reference object，所以如果要得到它的一份实体，必须这么做：<br>
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; Currency usd = Currency.get("USD");</span><br>
Currency class维护一个实体链表（list of instances）；我不能直接使用构造函数创建实体，因为Currency构造函数是private。<br><span style="background-color: rgb(211, 211, 211);">
new Currency("USD").equals(new Currency("USD"));&nbsp;&nbsp;  //return false</span><br>
<br>
要把一个<span style="font-weight: bold;">reference</span> object变成<span style="font-weight: bold;">value</span> object，关键动作是：检查它是否为immutable（不可变）。如果不是，我就不能使用本项重构，因为mutable（可变的）value object会造成令人苦恼的别名现象（aliasing）。<br>
<br>
在这里，<span style="font-weight: bold;">Currency</span>对象是不可变的，所以下一步就是为它定义equals（）：<br>
<span style="background-color: rgb(211, 211, 211);">public boolean equals(Object arg) {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; if(!(arg instanceof Currency)) return false;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; Currency other = (Currency)arg;</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; return (_code.equals(other._code));</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br style="background-color: rgb(211, 211, 211);">
<br>
如果我定义equals（），我必须同时定义hashCode（）。实现hashCode（）有个简单办法：读取equals（）使用的所有值域的hash codes，然后对它们进行<span style="font-weight: bold;">bitwise xor</span>（^）操作。本例中这很容易实现，因为equals（）只使用了一个值域：<br>
<span style="background-color: rgb(211, 211, 211);">public int hashCode() {</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">&nbsp;&nbsp;&nbsp; return _code.hashCode():</span><br style="background-color: rgb(211, 211, 211);">
<span style="background-color: rgb(211, 211, 211);">}</span><br>
完成这两个函数后，我可以编译并测试。这两个函数的修改必须同时进行，否则依赖hashing的任何群集对象（collections，例如Hashtable、HashSet和HashMap）可能会产生意外行为。<br>
<br>
现在，我想创建多少个等值的Currency对象就创建多少个。我还可以把构造函数声明为public，直接以构造函数获取Currency实体，从而去掉Currency class中的<span style="font-weight: bold;">factory method</span>和[控制实体创建]的行为：<br>
<span style="background-color: rgb(211, 211, 211);">new Currency("USD").equals(new Currency("USD"));&nbsp;&nbsp; //now returns true</span><br>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12008.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 11:04 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12008.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Reference to Value）（3）--作法</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12006.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 02:46:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12006.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12006.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12006.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12006.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12006.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">作法</font></span>（Mechanics)<br>
<ul>
  <ul>
    <li>检查重构对象是否为immutable（不可变）对象，或是否可修改为不可变对象。</li>
    <ul>
      <ul>
        <li>==》如果该对象目前还是immutable，就使用<span style="font-weight: bold;">Remove Setting Method</span>（300），直到它成为immutable为止。</li>
        <li>如果无法将该对象修改为immutable，就放弃使用本项重构。</li>
      </ul>
    </ul>
    <li>建立equals（）和hashCode（）。</li>
    <li>编译，测试。</li>
    <li>考虑是否可以删除<span style="font-weight: bold;">factory method</span>，并将构造函数声明为public。<br>
    </li>
  </ul>
</ul>
<img src ="http://www.blogjava.net/ivanwan/aggbug/12006.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 10:46 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12006.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Reference to value）（2）--动机 </title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12005.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 02:34:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12005.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12005.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12005.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12005.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12005.html</trackback:ping><description><![CDATA[<span style="font-weight: bold;"><font size="6">动机</font></span>（Motivation）<br>
在分布系统和并发系统中，不可变的<span style="font-weight: bold;">value</span> object特别有用，因为你不须考虑它们的同步问题。<br>
<br>
<span style="font-weight: bold;">value</span> object有一个非常重要的特性：它们应该是不可变的（immutable）。无论何时只要你调用同一个对象的同一个查询函数，你都应该得到同样结果。如果保证了这一点，就可以放心地以多个对象表示相同事物（same thing）。如果<span style="font-weight: bold;">value</span> object是可变的（mutable），你就必须确保你对某一对象的修改会自动更新其他[代表同事物]的其他对象。这太痛苦了，与其如此还不如把它变成<span style="font-weight: bold;">reference</span> object。<br>
<br>
这里有必要澄清一下[不可变（immutable）]的意思。如果你以<span style="font-weight: bold;">Money</span> class表示[钱]的概念，其中有[币种]和[金额]两条信息，那么<span style="font-weight: bold;">Money</span>对象通常是一个不可变的<span style="font-weight: bold;">value</span> object。这并非意味你的薪资不能改变，而是意味：如果要改变你的薪资，你需要使用另一个崭新的<span style="font-weight: bold;">Money</span>对象来取代先有的<span style="font-weight: bold;">Money</span>对象，而不是在现有的<span style="font-weight: bold;">Money</span>对象上修改。你和<span style="font-weight: bold;">Money</span>对象之间的关系可以改变，但<span style="font-weight: bold;">Money</span>对象自身不能改变。<img src ="http://www.blogjava.net/ivanwan/aggbug/12005.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 10:34 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12005.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>重构三--重新组织数据（Change Reference to Value）（1）</title><link>http://www.blogjava.net/ivanwan/archive/2005/09/05/12003.html</link><dc:creator>ivaneeo</dc:creator><author>ivaneeo</author><pubDate>Mon, 05 Sep 2005 01:47:00 GMT</pubDate><guid>http://www.blogjava.net/ivanwan/archive/2005/09/05/12003.html</guid><wfw:comment>http://www.blogjava.net/ivanwan/comments/12003.html</wfw:comment><comments>http://www.blogjava.net/ivanwan/archive/2005/09/05/12003.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ivanwan/comments/commentRss/12003.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ivanwan/services/trackbacks/12003.html</trackback:ping><description><![CDATA[你有一个<span style="font-weight: bold;">reference</span> object（引用对象），很小且不可变（immutable），而且不易管理。<br style="font-weight: bold;">
<br style="font-weight: bold;">
<span style="font-weight: bold;">将它变成一个value</span> object<span style="font-weight: bold;">（实值对象）。<br>
<br>
<img src="http://www.blogjava.net/images/blogjava_net/ivanwan/pictures/Change%20Reference%20to%20Value.png" alt="Change Reference to Value.png" border="0" height="291" width="449"><br>
</span><img src ="http://www.blogjava.net/ivanwan/aggbug/12003.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ivanwan/" target="_blank">ivaneeo</a> 2005-09-05 09:47 <a href="http://www.blogjava.net/ivanwan/archive/2005/09/05/12003.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>