1. Classification 4/22/2011 1 Refactoring Improving The Design of Existing Code Ch. 7 Moving Features Between Objects Joe C Wu Developer, WSE(Web Services Engineering) Volume
2. Outline Ch. 7 Moving Features Between Objects Move Method (搬移函式) Move Field (搬移欄位) Extract Class (提練類別) Inline Class (將類別內聯化) Hide Delegate (隱藏「委託關係」) Remove Middle Man (移除中間人) Introduce Foreign Method (加入外加函式) Introduce Local Extension (引入區域性擴展) Summary Classification 4/22/2011 2
5. Move Method (搬移函式) 範例 Classification 4/22/2011 5 Class Account… double overdraftCharge() { if ( _type.isPremium()) { double result = 10; if (_dayOverdrawn > 7) result += (_daysOverdrawn – 7) * 0.85; return result; }// end if else return _daysOverdrawn * 1.75; } // end overdraftChrge double bankCharge () { double result = 4.5; if (_daysOverdrawn > 0) result += overdraftCharge(); return result; } // end bankCharge private AccountType _type; private int _daysOverdrawn; Suppose you wish to have other account types and decide to build an account type class to contain the methods
6. Move Method (搬移函式) Classification 4/22/2011 6 Class AccountType… several account types – diff method double overdraftCharge(intdaysOverdrawn) { if (isPremium()) { double result = 10; if (dayOverdrawn > 7) result += (daysOverdrawn – 7) * 0.85; return result; }// end if else return _daysOverdrawn * 1.75; } // end overdraftChrge Class Account… double bankCharge () { double result = 4.5; if (_days|Overdrawn |> 0) result += _type.overdraftCharge(_daysOverdrawn); return result; } // end bankCharge
8. Classification 4/22/2011 8 Move Field (搬移欄位) 動機 某個class的field,被別的class使用更多次。 透過Get/Set間接進行。 也移動使用這個field的使用者。(取決於介面是否保持一致) 使用Extract Class時,也可能需要搬移field,這時會先搬field. 作法 Encapsulate Field (Get/Set) Compile and Test. 在target class建立同樣的field,並建立Getting/Setting。 Compile target class. 取得target object (若現有field or method不能做到,建一個field來存。 將所有source field的引用,改為存取target field。 Compile and Test.
9. Classification 4/22/2011 9 Move Field (搬移欄位) 範例 Class Account… private AccoutnType _type; private double _interestRate; double interestForAmount)days (double amount, int days) { return )interestRate * amount * days /365; } // end interestForAmount Would like to move interestRate to the AccountType class because it is used more in that class.
10. Classification 4/22/2011 10 Move Field (搬移欄位) 範例 Class AccountType…. private double _interestRate; void setInterestRate (double arg) { _interestRate = arg; } double getInterestRate () { return _interestRate; } ………. double interestForAccount_days (double amount, int days) { return _type.getInterestRate() * amount * days/365; } // end interestForAccount_days Move _interestRate. In the original class reference get and set methods for the field.
11. Classification 4/22/2011 11 Extract Class (提練類別) 某個class做了應該由兩個classes做的事。 建立一個新的class,將相關的欄位和函式從舊class移至新class.
12. Classification 4/22/2011 12 Extract Class (提練類別) 動機 一個class應該是一個清楚的抽象性(abstract),處理一些明確的責任。 Class太大會不易理解。 某些資料和函式總是一起出現、某些資料常同時變化並彼此相依,表示他們該被分離出去。 作法 決定如何分解class所負責任。 建立新的class,將舊class的責任交給它。 建立舊class與新class之間的link。 Move Field -> Test Move Method -> Test 檢查、精簡每個class的介面。 決定是否讓新class曝光 => reference object / immutable value object
20. Classification 4/22/2011 20 Inline Class (將類別內聯化) Person martin = new Person(); martin.getOfficeTelephone().setAreaCode ("781"); Person martin = new Person(); martin.setAreaCode ("781");
23. Classification 4/22/2011 23 Hide Delegate (隱藏「委託關係」) 作法 對每一個委託關係中的函式,在server端建立一個簡單的委託函式(delegating method)。 調整客戶,令它只呼叫server提供的函式 Compile and Test 如果將來不再有任何客戶需要用到Delegate,便可移除server中的存取函式 Compile and Test
24. Classification 4/22/2011 24 Hide Delegate (隱藏「委託關係」) class Person { Department _department; public Department getDepartment() { return _department; } public void setDepartment(Department arg) { _department = arg; } } 範例 manager = john.getDepartment().getManager(); public Person getManager() { return _department.getManager(); } class Department { private String _chargeCode; private Person _manager; public Department(Person manager) { _manager = manager; } public Person getManager() { return _manager; } } manager = john.getManager();
26. Classification 4/22/2011 26 Remove Middle Man (移除中間人) 動機 Hide Delegate的代價 當客戶要用到delegate的新特性時,你就必須在server端添加一個簡單委託函式。delegate的特性(功能)愈來愈多,就愈來愈痛苦。 很難說什麼程度的隱藏才是合適的。 隨著系統的變化,改成合適的就好了。 重構的意義就在於:你永遠不必說什麼對不起,只要把出問題的地方修補好就行了。 作法 建立一個函式,用以取代delegate(受托物件)。 對於每個委託函式(delegate method),在server中刪除該函式,並將「客戶對該函式的呼叫」替換為「對delegate的呼叫」。 Compile and Test
27. Classification 4/22/2011 27 Remove Middle Man (移除中間人) 範例 class Person { Department _department; public Person getManager() { return _department.getManager(); } } class Department { private Person _manager; public Department(Person manager) { _manager = manager; } } manager = john.getManager(); class Person { public Department getDepartment() { return _department; } } manager = john.getDepartment().getManager();
28. Classification 4/22/2011 28 Introduce Foreign Method (加入外加函式) 你所使用的server class需要一個函式,但你無法修改這個class。 在client class中建立一個函式,並以一個server class實體作為第一引數(argument)。 Date newStart = new Date (previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1); Date newStart = nextDay(previousEnd); private static Date nextDay(Date arg) { return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1); }
34. Classification 4/22/2011 34 Introduce Local Extension (引入區域性擴展) 範例(Subclass) class MfDateSub extends Date { public MfDateSub (String dateString) { super (dateString); } public MfDateSub (Date arg) { super (arg.getTime()); } Date nextDay() { return new Date (getYear(),getMonth(), getDate() + 1); } } 轉型建構式(converting constructors)
35. Classification 4/22/2011 35 Introduce Local Extension (引入區域性擴展) class MfDateWrap { private Date _original; public MfDateWrap(String dateString) { _original = new Date(dateString); } public MfDateWrap(Date arg) { _original = arg; } // 為原始類別的所有函式提供委託函式… public intgetYear() {return _original.getYear();} public intgetMonth() {return _original.getMonth();} public intgetDate() {return _original.getDate();} public boolean equals(MfDateWraparg) {return (toDate().equals(arg.toDate()));} Date nextDay() { return new Date(getYear(), getMonth(), getDate() + 1); } } 範例(Wrapper) 如何處理「接受原始類別之實體為參數」的函式? 只是執行一個單純的委託動作(delegate) public boolean after (Date arg) aWrapper.after(aDate) aWrapper.after(anotherWrapper) aDate.after(aWrapper)
36. Introduce Local Extension (引入區域性擴展) 範例(Wrapper) 上一頁”after” method這種overriding的目的是為了向用戶隱藏wrapper的存在,這是一個好策略。 不過在某些系統所提供的函式(如equals)會出問題。 public booleanequals (Date arg) // causes problems 這樣做是危險的,因為Java系統的其他部份都認為equals符合交換律:如果a.equals(b)為真,b.equals(a)也必為真。 違反這樣的規則將使我遭遇一大堆莫名其妙的錯誤,但是又無法同時修改Date,所以建議向用戶曝露「我進行了包裝」 public booleanequalsDate(Date arg) public booleanequalsDate(MfDateWraparg) Subclass則不會有這問題,只要不覆寫original class中的函式。 一般來說不會在extension class中覆寫original class的函式,只會添加新函式。 Classification 4/22/2011 36
37. Classification 4/22/2011 37 Summary 決定把責任放在哪兒? 重構的基本手法 Move Field > Move Method Class責任 過多 或是 過少 Extract Class V.S Inline Class Class使用另一Class Hide Delegate將關係隱藏起來 因為Hide delegate導致擁有者的介面經常變化 Remove Middle Man 當我不能存取某個class的源碼,又想把其他責任移進去 Introduce Foreign Method (只加一、二個函式) Introduce Local Extension