重構—改善既有程式的設計(chapter 7)

1,873 views

Published on

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,873
On SlideShare
0
From Embeds
0
Number of Embeds
36
Actions
Shares
0
Downloads
85
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

重構—改善既有程式的設計(chapter 7)

  1. 1. Classification 4/22/2011<br />1<br />Refactoring Improving The Design of Existing Code<br />Ch. 7 Moving Features Between Objects<br />Joe C Wu Developer, WSE(Web Services Engineering) Volume<br />
  2. 2. Outline<br />Ch. 7 Moving Features Between Objects<br />Move Method (搬移函式)<br />Move Field (搬移欄位)<br />Extract Class (提練類別)<br />Inline Class (將類別內聯化)<br />Hide Delegate (隱藏「委託關係」)<br />Remove Middle Man (移除中間人)<br />Introduce Foreign Method (加入外加函式)<br />Introduce Local Extension (引入區域性擴展)<br />Summary<br />Classification 4/22/2011<br />2<br />
  3. 3. Classification 4/22/2011<br />3<br />Move Method (搬移函式)<br />在該函式最常引用的class中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式(delegating method),或是將舊函式完全移除。<br />
  4. 4. Classification 4/22/2011<br />4<br />Move Method (搬移函式)<br />動機<br />當一個Class有太多的行為.<br />當一個Class與另個Class有太多合作而型成高度耦合(highly coupled).<br />使系統中的classes更簡單。<br />作法<br />這個函式與哪個物件的交流比較多?<br />檢查source class定義的method所使用的一切features.<br />檢查sub class, super class.<br />搬移source method to target method -> Compile target class.<br />決定如何從source 正確引用target object.<br />修改source method 成為delegating method. -> Compile and Test<br />刪除source method 或將它當作一個delegating method保留下來。<br />將source class中對source method的引用替換為target method.<br />Compile and Test.<br />很難決定 -> 或許移動這個函式與否,並不那麼重要。<br />
  5. 5. Move Method (搬移函式)<br />範例<br />Classification 4/22/2011<br />5<br />Class Account…<br /> double overdraftCharge() {<br /> if ( _type.isPremium()) {<br /> double result = 10;<br /> if (_dayOverdrawn > 7) result += (_daysOverdrawn – 7) * 0.85;<br /> return result;<br /> }// end if<br /> else return _daysOverdrawn * 1.75;<br />} // end overdraftChrge<br />double bankCharge () {<br /> double result = 4.5;<br /> if (_daysOverdrawn > 0) result += overdraftCharge();<br /> return result;<br />} // end bankCharge<br />private AccountType _type;<br />private int _daysOverdrawn;<br />Suppose you wish to have other account types <br />and decide to build an account type class to contain the methods<br />
  6. 6. Move Method (搬移函式)<br />Classification 4/22/2011<br />6<br />Class AccountType… several account types – diff method <br /> double overdraftCharge(intdaysOverdrawn) { <br /> if (isPremium()) {<br /> double result = 10;<br /> if (dayOverdrawn > 7) result += (daysOverdrawn – 7) * 0.85;<br /> return result;<br /> }// end if<br /> else return _daysOverdrawn * 1.75;<br />} // end overdraftChrge<br />Class Account… <br /> double bankCharge () {<br /> double result = 4.5;<br /> if (_days|Overdrawn |> 0) result += _type.overdraftCharge(_daysOverdrawn);<br /> return result;<br />} // end bankCharge<br />
  7. 7. Classification 4/22/2011<br />7<br />Move Field (搬移欄位)<br />某個class的field,被別的class使用更多次。<br />在target class建立一個new field,修改source field的所有用戶,令它們改用new field.<br />
  8. 8. Classification 4/22/2011<br />8<br />Move Field (搬移欄位)<br />動機<br />某個class的field,被別的class使用更多次。<br />透過Get/Set間接進行。<br />也移動使用這個field的使用者。(取決於介面是否保持一致)<br />使用Extract Class時,也可能需要搬移field,這時會先搬field.<br />作法<br />Encapsulate Field (Get/Set)<br />Compile and Test.<br />在target class建立同樣的field,並建立Getting/Setting。<br />Compile target class.<br />取得target object (若現有field or method不能做到,建一個field來存。<br />將所有source field的引用,改為存取target field。<br />Compile and Test.<br />
  9. 9. Classification 4/22/2011<br />9<br />Move Field (搬移欄位)<br />範例<br />Class Account…<br /> private AccoutnType _type;<br /> private double _interestRate;<br /> double interestForAmount)days (double amount, int days) {<br /> return )interestRate * amount * days /365;<br /> } // end interestForAmount<br />Would like to move interestRate to the AccountType class because it is used more in that class.<br />
  10. 10. Classification 4/22/2011<br />10<br />Move Field (搬移欄位)<br />範例<br />Class AccountType….<br /> private double _interestRate;<br />void setInterestRate (double arg) { _interestRate = arg; }<br /> double getInterestRate () { return _interestRate; }<br />……….<br />double interestForAccount_days (double amount, int days) {<br /> return _type.getInterestRate() * amount * days/365;<br />} // end interestForAccount_days<br />Move _interestRate.<br />In the original class<br />reference get and set methods for the field.<br />
  11. 11. Classification 4/22/2011<br />11<br />Extract Class (提練類別)<br />某個class做了應該由兩個classes做的事。<br />建立一個新的class,將相關的欄位和函式從舊class移至新class.<br />
  12. 12. Classification 4/22/2011<br />12<br />Extract Class (提練類別)<br />動機<br />一個class應該是一個清楚的抽象性(abstract),處理一些明確的責任。<br />Class太大會不易理解。<br />某些資料和函式總是一起出現、某些資料常同時變化並彼此相依,表示他們該被分離出去。<br />作法<br />決定如何分解class所負責任。<br />建立新的class,將舊class的責任交給它。<br />建立舊class與新class之間的link。<br />Move Field -> Test<br />Move Method -> Test<br />檢查、精簡每個class的介面。<br />決定是否讓新class曝光 => reference object / immutable value object<br />
  13. 13. Classification 4/22/2011<br />13<br />class Person<br />{<br /> public String getName()<br />{<br /> return _name;<br />}<br /> public String getTelephoneNumber()<br />{<br /> return ("(" + _officeAreaCode + ") " + _officeNumber);<br />}<br /> String getOfficeAreaCode()<br />{<br /> return _officeAreaCode;<br />}<br /> void setOfficeAreaCode(String arg)<br />{<br /> _officeAreaCode = arg;<br />}<br /> String getOfficeNumber()<br />{<br /> return _officeNumber;<br />}<br /> void setOfficeNumber(String arg)<br />{<br /> _officeNumber = arg;<br />}<br /> private String _name;<br /> private String _officeAreaCode;<br /> private String _officeNumber;<br />}<br />Extract Class (提練類別)<br />範例<br />
  14. 14. Classification 4/22/2011<br />14<br />Extract Class (提練類別)<br />Move Field<br />class Person<br />{<br /> private TelephoneNumber_officeTelephone= new TelephoneNumber();<br /> public String getTelephoneNumber()<br />{<br /> return ("(" + getOfficeAreaCode() + ") " + _officeNumber);<br />}<br /> String getOfficeAreaCode()<br />{<br /> return _officeTelephone.getAreaCode();<br />}<br /> void setOfficeAreaCode(String arg)<br />{<br /> _officeTelephone.setAreaCode(arg);<br />}<br />}<br />class TelephoneNumber<br />{<br /> String getAreaCode()<br />{<br /> return _areaCode;<br />}<br /> void setAreaCode(String arg)<br />{<br /> _areaCode = arg;<br />}<br /> private String _areaCode;<br />}<br />
  15. 15. Extract Class (提練類別)<br />Move Method<br />Classification 4/22/2011<br />15<br />class TelephoneNumber<br />{<br /> public String getTelephoneNumber()<br />{<br /> return ("(" + _areaCode + ") " + _number);<br />}<br /> String getAreaCode()<br />{<br /> return _areaCode;<br />}<br /> void setAreaCode(String arg)<br />{<br /> _areaCode = arg;<br />}<br /> String getNumber()<br />{<br /> return _number;<br />}<br /> void setNumber(String arg)<br />{<br /> _number = arg;<br />}<br /> private String _number;<br /> private String _areaCode;<br />}<br />class Person<br />{<br /> public String getName()<br />{<br /> return _name;<br />}<br /> public String getTelephoneNumber()<br />{<br /> return _officeTelephone.getTelephoneNumber();<br />}<br />TelephoneNumbergetOfficeTelephone()<br />{<br /> return _officeTelephone;<br />}<br /> private String _name;<br /> private TelephoneNumber _officeTelephone = new TelephoneNumber();<br />}<br />
  16. 16. Extract Class (提練類別)<br />TelephoneNumber<br />Public?<br />Private?<br />Protected?<br />若public新的class,要考慮別名(aliasing)的問題<br />允許任何物件修改TelephoneNumber物件的任何部份<br />使它成為Reference Object。(person為的存取點) (Ch.8)<br />不許任何人”不透過Person就修改它”<br />設為immutable或是提供immutable interface。<br />先複製一個TelephoneNumber物件,將這個物件傳給用戶,當然這也會造成一定程度的迷惑。<br />你可以為提煉後的class加鎖(lock)。但要小心鎖定的問題。<br />Classification 4/22/2011<br />16<br />
  17. 17. Classification 4/22/2011<br />17<br />Inline Class (將類別內聯化)<br />你的某個class沒有做太多事情(沒有承擔足夠的責任)<br />將class的所有特性搬移到另一個class中,然後移除原class.<br />
  18. 18. Classification 4/22/2011<br />18<br />Inline Class (將類別內聯化)<br />動機<br />如果一個class不再承擔足夠的責任,不再有單獨存在的理由。<br />挑選最頻繁使用這個「萎縮class」的用戶,以Inline Class手法塞進去。<br />作法<br />在absorbing class(合併端class)宣告source class的public協定,並將所有函式委託(delegate)至source class.<br />修改source class的引用點->absorbing class。<br />Compile and Test<br />Move Method and Move Field,將source class的特性移過去。<br />幫source class舉行一個簡單的喪禮。<br />
  19. 19. Classification 4/22/2011<br />19<br />Inline Class (將類別內聯化)<br />class TelephoneNumber<br />{<br /> public String getTelephoneNumber()<br />{<br /> return ("(" + _areaCode + ") " + _number);<br />}<br /> String getAreaCode()<br />{<br /> return _areaCode;<br />}<br /> void setAreaCode(String arg)<br />{<br /> _areaCode = arg;<br />}<br /> String getNumber()<br />{<br /> return _number;<br />}<br /> void setNumber(String arg)<br />{<br /> _number = arg;<br />}<br /> private String _number;<br /> private String _areaCode;<br />}<br />範例<br />class Person<br />{<br /> public String getName()<br />{return _name;}<br /> public String getTelephoneNumber()<br />{return _officeTelephone.getTelephoneNumber();}<br />TelephoneNumbergetOfficeTelephone()<br />{return _officeTelephone;}<br /> private String _name;<br /> private TelephoneNumber _officeTelephone = new TelephoneNumber();<br /> String getAreaCode()<br />{ return _officeTelephone.getAreaCode();}<br /> void setAreaCode(String arg)<br />{ _officeTelephone.setAreaCode(arg);}<br /> String getNumber()<br />{ return _officeTelephone.getNumber();}<br /> void setNumber(String arg)<br />{ _officeTelephone.setNumber(arg);}<br />}<br />
  20. 20. Classification 4/22/2011<br />20<br />Inline Class (將類別內聯化)<br />Person martin = new Person();<br />martin.getOfficeTelephone().setAreaCode ("781");<br />Person martin = new Person();<br />martin.setAreaCode ("781");<br />
  21. 21. Classification 4/22/2011<br />21<br />Hide Delegate (隱藏「委託關係」)<br />客戶直接呼叫其server object(服務物件)的delegate class。<br />在Server端建立客戶所需的所有函式,用以隱藏委托關係(delegation)。<br />
  22. 22. Classification 4/22/2011<br />22<br />Hide Delegate (隱藏「委託關係」)<br />動機<br />封裝即使不是物件的最關鍵特徵,也是最關鍵特徵之一。<br />如果某個客戶呼叫了「建立於server object的某個欄位基礎之上」的函式,客戶就必需知道這個委託物件(delegate object),萬一委託關係發生變化,客戶也得相應變化。<br />將委託關係隱藏起來,去除這種依存性,將變化限制在server中,不會波及客戶。<br />
  23. 23. Classification 4/22/2011<br />23<br />Hide Delegate (隱藏「委託關係」)<br />作法<br />對每一個委託關係中的函式,在server端建立一個簡單的委託函式(delegating method)。<br />調整客戶,令它只呼叫server提供的函式<br />Compile and Test<br />如果將來不再有任何客戶需要用到Delegate,便可移除server中的存取函式<br />Compile and Test<br />
  24. 24. Classification 4/22/2011<br />24<br />Hide Delegate (隱藏「委託關係」)<br />class Person<br />{<br /> Department _department;<br /> public Department getDepartment()<br />{<br /> return _department;<br />}<br /> public void setDepartment(Department arg)<br />{<br /> _department = arg;<br />}<br />}<br />範例<br />manager = john.getDepartment().getManager();<br /> public Person getManager()<br />{<br /> return _department.getManager();<br />}<br />class Department<br />{<br /> private String _chargeCode;<br /> private Person _manager;<br /> public Department(Person manager)<br />{<br /> _manager = manager;<br />}<br /> public Person getManager()<br />{<br /> return _manager;<br />}<br />}<br />manager = john.getManager();<br />
  25. 25. Classification 4/22/2011<br />25<br />Remove Middle Man (移除中間人)<br />某個class做了過多的簡單委託動作(simple delegation)。<br />讓客戶直接呼叫delegate(受託類別)。<br />
  26. 26. Classification 4/22/2011<br />26<br />Remove Middle Man (移除中間人)<br />動機<br />Hide Delegate的代價<br />當客戶要用到delegate的新特性時,你就必須在server端添加一個簡單委託函式。delegate的特性(功能)愈來愈多,就愈來愈痛苦。<br />很難說什麼程度的隱藏才是合適的。<br />隨著系統的變化,改成合適的就好了。<br />重構的意義就在於:你永遠不必說什麼對不起,只要把出問題的地方修補好就行了。<br />作法<br />建立一個函式,用以取代delegate(受托物件)。<br />對於每個委託函式(delegate method),在server中刪除該函式,並將「客戶對該函式的呼叫」替換為「對delegate的呼叫」。<br />Compile and Test<br />
  27. 27. Classification 4/22/2011<br />27<br />Remove Middle Man (移除中間人)<br />範例<br />class Person<br />{<br /> Department _department;<br /> public Person getManager()<br />{<br /> return _department.getManager();<br />}<br />}<br />class Department<br />{<br /> private Person _manager;<br /> public Department(Person manager)<br />{<br /> _manager = manager;<br />}<br />}<br />manager = john.getManager();<br />class Person<br />{<br /> public Department getDepartment()<br />{<br /> return _department;<br />}<br />}<br />manager = john.getDepartment().getManager();<br />
  28. 28. Classification 4/22/2011<br />28<br />Introduce Foreign Method (加入外加函式)<br />你所使用的server class需要一個函式,但你無法修改這個class。<br />在client class中建立一個函式,並以一個server class實體作為第一引數(argument)。<br />Date newStart = new Date (previousEnd.getYear(),<br />previousEnd.getMonth(), previousEnd.getDate() + 1);<br />Date newStart = nextDay(previousEnd);<br />private static Date nextDay(Date arg) {<br /> return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1);<br />}<br />
  29. 29. Classification 4/22/2011<br />29<br />Introduce Foreign Method (加入外加函式)<br />動機<br />你正在使用一個class,它真的很好,為你提供了你想要的所有服務。<br />當你需要一個新服務,這個class卻無法供應…<br />如果可以修改源碼,你便可以加一個新函式,如果不能,你就得在客戶端編碼,補足你要的函式。<br />如果你需要使用很多次,你就得不斷重初這些程式碼…<br />如果你以外加函式實現一個新功能,那就是一個明確信號:<br />這個函式原本應該在提供服務的class中加以實現。<br />如果一個server class加入了大量的外加函式,你就不應該再使用本項重構,而應該使用Introduce Local Extension。<br />
  30. 30. Classification 4/22/2011<br />30<br />Introduce Foreign Method (加入外加函式)<br />作法<br />在client class中建立一個函式,用來提供你需要的功能。<br />這個函式不應該取用client class的任何特性,如果需要,用參數傳給它。<br />以server class實體作為該函式的第一個參數。<br />將該函式注釋為「外加函式(foreign method),應在server class實現。<br />範例<br />Date newStart = new Date (previousEnd.getYear(),<br />previousEnd.getMonth(), previousEnd.getDate() + 1);<br />Date newStart = nextDay(previousEnd);<br />private static Date nextDay(Date arg) {<br /> return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1);<br />}<br />
  31. 31. Classification 4/22/2011<br />31<br />Introduce Local Extension (引入區域性擴展)<br />你所使用的server class需要一些額外函式,但你無法修改這個class。<br />建立一個新class,使用包含這些額外函式。讓這個擴展品成盥source class的sub class(子類別)或wrapper(外覆類別)。<br />
  32. 32. Classification 4/22/2011<br />32<br />Introduce Local Extension (引入區域性擴展)<br />動機<br />如果只需要一兩個函式,可以使用Introduce Foreign Method。<br />超果兩個,可以用這個方法將這些函式組織在一起,這種方式稱之為local extension(區域性擴展)<br />是一個獨立的class,卻也是其extended class的subtype。<br />堅持「函式和資料應該被包裝在形式良好的單元內」。<br />如果你一直把本該在extension class的程式零散放在其他classes中,只會讓其他這些classes變得過分複雜,並使得其中函式難以被復用。<br />local extension有兩種作法<br />Subclass<br />必須在物件創建期實施。<br />必須新增一個class,但是當原物件也被使用時,就要考慮維護兩份資料的問題。<br />Wrapper<br />通常首選subclass,因為工作量比較少。<br />
  33. 33. Classification 4/22/2011<br />33<br />Introduce Local Extension (引入區域性擴展)<br />作法<br />建立一個extension class,將它作為原物(原類別)的subclass或wrapper。<br />在extension class中加入轉型建構式(converting constructors)。<br />Converting constructors: 接受原物作為參數<br />在extension class中加入新特性。<br />根據需要,將原物(original)替換為擴展物(extension)。<br />將「針對原始類別而定義的所有外加函式(foreign ethods)」搬移到extension class中。<br />
  34. 34. Classification 4/22/2011<br />34<br />Introduce Local Extension (引入區域性擴展)<br />範例(Subclass)<br />class MfDateSub extends Date<br />{<br /> public MfDateSub (String dateString) {<br /> super (dateString);<br />}<br /> public MfDateSub (Date arg) {<br /> super (arg.getTime());<br />}<br /> Date nextDay() {<br /> return new Date (getYear(),getMonth(), getDate() + 1);<br />}<br />}<br />轉型建構式(converting constructors)<br />
  35. 35. Classification 4/22/2011<br />35<br />Introduce Local Extension (引入區域性擴展)<br />class MfDateWrap<br />{<br /> private Date _original;<br /> public MfDateWrap(String dateString)<br />{<br /> _original = new Date(dateString);<br />}<br /> public MfDateWrap(Date arg)<br />{<br />_original = arg;<br />}<br />// 為原始類別的所有函式提供委託函式…<br /> public intgetYear()<br /> {return _original.getYear();}<br /> public intgetMonth()<br /> {return _original.getMonth();}<br /> public intgetDate()<br /> {return _original.getDate();}<br /> public boolean equals(MfDateWraparg)<br /> {return (toDate().equals(arg.toDate()));}<br /> Date nextDay()<br />{<br /> return new Date(getYear(), getMonth(), getDate() + 1);<br />}<br />}<br />範例(Wrapper)<br />如何處理「接受原始類別之實體為參數」的函式?<br />只是執行一個單純的委託動作(delegate)<br />public boolean after (Date arg)<br />aWrapper.after(aDate)<br />aWrapper.after(anotherWrapper)<br />aDate.after(aWrapper)<br />
  36. 36. Introduce Local Extension (引入區域性擴展)<br />範例(Wrapper)<br />上一頁”after” method這種overriding的目的是為了向用戶隱藏wrapper的存在,這是一個好策略。<br />不過在某些系統所提供的函式(如equals)會出問題。<br />public booleanequals (Date arg) // causes problems<br />這樣做是危險的,因為Java系統的其他部份都認為equals符合交換律:如果a.equals(b)為真,b.equals(a)也必為真。<br />違反這樣的規則將使我遭遇一大堆莫名其妙的錯誤,但是又無法同時修改Date,所以建議向用戶曝露「我進行了包裝」<br />public booleanequalsDate(Date arg)<br />public booleanequalsDate(MfDateWraparg)<br />Subclass則不會有這問題,只要不覆寫original class中的函式。<br />一般來說不會在extension class中覆寫original class的函式,只會添加新函式。<br />Classification 4/22/2011<br />36<br />
  37. 37. Classification 4/22/2011<br />37<br />Summary<br />決定把責任放在哪兒?<br />重構的基本手法<br />Move Field > Move Method<br />Class責任 過多 或是 過少<br />Extract Class V.S Inline Class<br />Class使用另一Class<br />Hide Delegate將關係隱藏起來<br />因為Hide delegate導致擁有者的介面經常變化<br />Remove Middle Man<br />當我不能存取某個class的源碼,又想把其他責任移進去<br />Introduce Foreign Method (只加一、二個函式)<br />Introduce Local Extension<br />
  38. 38. Classification 4/22/2011<br />38<br />

×