Upload
chris-huang
View
1.548
Download
4
Embed Size (px)
DESCRIPTION
Citation preview
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 1
Refactoring Improving The Design of Existing Code
Ch. 7 Moving Features Between ObjectsJoe C Wu Developer, WSE(Web Services Engineering) Volume
Copyright 2009 Trend Micro Inc.
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 04/10/2023 2
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 3
Move Method (搬移函式 )
• 在該函式最常引用的 class 中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式 (delegating method) ,或是將舊函式完全移除。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 4
Move Method (搬移函式 )
• 動機– 當一個 Class 有太多的行為 .– 當一個 Class 與另個 Class 有太多合作而型成高度耦合 (highly
coupled).– 使系統中的 classes 更簡單。
• 作法– 這個函式與哪個物件的交流比較多 ?
• 檢查 source class 定義的 method 所使用的一切 features.• 檢查 sub class, super class.• 搬移 source method to target method -> Compile target class.• 決定如何從 source 正確引用 target object.• 修改 source method 成為 delegating method. -> Compile and Test• 刪除 source method 或將它當作一個 delegating method 保留下來。• 將 source class 中對 source method 的引用替換為 target method.• Compile and Test.
– 很難決定 -> 或許移動這個函式與否,並不那麼重要。
Copyright 2009 Trend Micro Inc. 5
Move Method (搬移函式 )
• 範例
Classification 04/10/2023
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
Copyright 2009 Trend Micro Inc. 6
Move Method (搬移函式 )
Classification 04/10/2023
Class AccountType… several account types – diff method
double overdraftCharge(int daysOverdrawn) {
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
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 7
Move Field (搬移欄位 )
• 某個 class 的 field ,被別的 class 使用更多次。• 在 target class 建立一個 new field ,修改 source field 的所
有用戶,令它們改用 new field.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 11
Extract Class (提練類別 )
• 某個 class 做了應該由兩個 classes 做的事。• 建立一個新的 class ,將相關的欄位和函式從舊 class 移至
新 class.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 13
Extract Class (提練類別 )
• 範例
class Person{ public String getName() { return _name; } public String getTelephoneNumber() { return ("(" + _officeAreaCode + ") " + _officeNumber); } String getOfficeAreaCode() { return _officeAreaCode; } void setOfficeAreaCode(String arg) { _officeAreaCode = arg; } String getOfficeNumber() { return _officeNumber; } void setOfficeNumber(String arg) { _officeNumber = arg; } private String _name; private String _officeAreaCode; private String _officeNumber;}
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 14
Extract Class (提練類別 )
• Move Fieldclass Person{ private TelephoneNumber _officeTelephone = new TelephoneNumber();
public String getTelephoneNumber() { return ("(" + getOfficeAreaCode() + ") " + _officeNumber); } String getOfficeAreaCode() { return _officeTelephone.getAreaCode(); } void setOfficeAreaCode(String arg) { _officeTelephone.setAreaCode(arg); }}
class TelephoneNumber{ String getAreaCode() { return _areaCode; } void setAreaCode(String arg) { _areaCode = arg; } private String _areaCode;}
Copyright 2009 Trend Micro Inc. 15
Extract Class (提練類別 )
• Move Method
Classification 04/10/2023
class Person{ public String getName() { return _name; } public String getTelephoneNumber() { return _officeTelephone.getTelephoneNumber(); } TelephoneNumber getOfficeTelephone() { return _officeTelephone; } private String _name; private TelephoneNumber _officeTelephone = new TelephoneNumber();}
class TelephoneNumber{ public String getTelephoneNumber() { return ("(" + _areaCode + ") " + _number); } String getAreaCode() { return _areaCode; } void setAreaCode(String arg) { _areaCode = arg; } String getNumber() { return _number; } void setNumber(String arg) { _number = arg; } private String _number; private String _areaCode;}
Copyright 2009 Trend Micro Inc. 16
Extract Class (提練類別 )
• TelephoneNumber– Public?– Private?– Protected?
• 若 public新的 class,要考慮別名 (aliasing)的問題– 允許任何物件修改 TelephoneNumber物件的任何部份
• 使它成為 Reference Object。 (person為的存取點 ) (Ch.8)– 不許任何人”不透過 Person就修改它”
• 設為 immutable或是提供 immutable interface。– 先複製一個 TelephoneNumber物件,將這個物件傳給用戶,當然這也會造成一定程度的迷惑。
• 你可以為提煉後的 class加鎖 (lock)。但要小心鎖定的問題。
Classification 04/10/2023
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 17
Inline Class (將類別內聯化 )
• 你的某個 class 沒有做太多事情 ( 沒有承擔足夠的責任 )
• 將 class 的所有特性搬移到另一個 class 中,然後移除原class.
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 18
Inline Class (將類別內聯化 )
• 動機– 如果一個 class 不再承擔足夠的責任,不再有單獨存在的理由。– 挑選最頻繁使用這個「萎縮 class 」的用戶,以 Inline Class 手法塞
進去。• 作法
– 在 absorbing class( 合併端 class) 宣告 source class 的 public 協定,並將所有函式委託 (delegate) 至 source class.
– 修改 source class 的引用點 ->absorbing class 。– Compile and Test– Move Method and Move Field ,將 source class 的特性移過去。– 幫 source class 舉行一個簡單的喪禮。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 19
Inline Class (將類別內聯化 )
• 範例class Person{ public String getName() {return _name;} public String getTelephoneNumber() {return _officeTelephone.getTelephoneNumber();} TelephoneNumber getOfficeTelephone() {return _officeTelephone;} private String _name; private TelephoneNumber _officeTelephone = new TelephoneNumber();
String getAreaCode() { return _officeTelephone.getAreaCode(); }
void setAreaCode(String arg) { _officeTelephone.setAreaCode(arg); }
String getNumber() { return _officeTelephone.getNumber(); }
void setNumber(String arg) { _officeTelephone.setNumber(arg); }}
class TelephoneNumber{ public String getTelephoneNumber() { return ("(" + _areaCode + ") " + _number); } String getAreaCode() { return _areaCode; } void setAreaCode(String arg) { _areaCode = arg; } String getNumber() { return _number; } void setNumber(String arg) { _number = arg; } private String _number; private String _areaCode;}
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 20
Inline Class (將類別內聯化 )
Person martin = new Person();martin.getOfficeTelephone().setAreaCode ("781");
Person martin = new Person();martin.setAreaCode ("781");
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 21
Hide Delegate (隱藏「委託關係」 )
• 客戶直接呼叫其 server object( 服務物件 ) 的 delegate class 。• 在 Server 端建立客戶所需的所有函式,用以隱藏委托關係
(delegation) 。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 22
Hide Delegate (隱藏「委託關係」 )
• 動機– 封裝即使不是物件的最關鍵特徵,也是最關鍵特徵之一。– 如果某個客戶呼叫了「建立於 server object 的某個欄位基礎之上」
的函式,客戶就必需知道這個委託物件 (delegate object) ,萬一委託關係發生變化,客戶也得相應變化。
– 將委託關係隱藏起來,去除這種依存性,將變化限制在 server 中,不會波及客戶。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 23
Hide Delegate (隱藏「委託關係」 )
• 作法– 對每一個委託關係中的函式,在 server 端建立一個簡單的委託函式
(delegating method) 。– 調整客戶,令它只呼叫 server 提供的函式– Compile and Test– 如果將來不再有任何客戶需要用到 Delegate ,便可移除 server 中的
存取函式– Compile and Test
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 24
Hide Delegate (隱藏「委託關係」 )
• 範例class Person{ Department _department; public Department getDepartment() { return _department; } public void setDepartment(Department arg) { _department = arg; }}
class Department{ private String _chargeCode; private Person _manager; public Department(Person manager) { _manager = manager; } public Person getManager() { return _manager; }}
manager = john.getDepartment().getManager();
public Person getManager() { return _department.getManager(); }
manager = john.getManager();
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 25
Remove Middle Man (移除中間人 )
• 某個 class 做了過多的簡單委託動作 (simple delegation) 。• 讓客戶直接呼叫 delegate(受託類別 ) 。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 26
Remove Middle Man (移除中間人 )
• 動機– Hide Delegate 的代價
• 當客戶要用到 delegate 的新特性時,你就必須在 server 端添加一個簡單委託函式。 delegate 的特性 (功能 )愈來愈多,就愈來愈痛苦。
– 很難說什麼程度的隱藏才是合適的。– 隨著系統的變化,改成合適的就好了。– 重構的意義就在於:你永遠不必說什麼對不起,只要把出問題的地方
修補好就行了。• 作法
– 建立一個函式,用以取代 delegate(受托物件 ) 。– 對於每個委託函式 (delegate method) ,在 server 中刪除該函式,並
將「客戶對該函式的呼叫」替換為「對 delegate 的呼叫」。– Compile and Test
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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();
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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);
}
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 29
Introduce Foreign Method (加入外加函式 )
• 動機– 你正在使用一個 class ,它真的很好,為你提供了你想要的所有服務。– 當你需要一個新服務,這個 class卻無法供應…– 如果可以修改源碼,你便可以加一個新函式,如果不能,你就得在客
戶端編碼,補足你要的函式。– 如果你需要使用很多次,你就得不斷重初這些程式碼…– 如果你以外加函式實現一個新功能,那就是一個明確信號:
• 這個函式原本應該在提供服務的 class 中加以實現。– 如果一個 server class 加入了大量的外加函式,你就不應該再使用本項重構,而應該使用 Introduce Local Extension 。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 30
Introduce Foreign Method (加入外加函式 )
• 作法– 在 client class 中建立一個函式,用來提供你需要的功能。
• 這個函式不應該取用 client class 的任何特性,如果需要,用參數傳給它。– 以 server class實體作為該函式的第一個參數。– 將該函式注釋為「外加函式 (foreign method) ,應在 server class實
現。• 範例
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);
}
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 31
Introduce Local Extension (引入區域性擴展 )
• 你所使用的 server class 需要一些額外函式,但你無法修改這個class 。
• 建立一個新 class ,使用包含這些額外函式。讓這個擴展品成盥 source class 的 sub class(子類別 ) 或 wrapper( 外覆類別 ) 。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 32
Introduce Local Extension (引入區域性擴展 )
• 動機– 如果只需要一兩個函式,可以使用 Introduce Foreign Method 。– 超果兩個,可以用這個方法將這些函式組織在一起,這種方式稱之為
local extension( 區域性擴展 )• 是一個獨立的 class ,卻也是其 extended class 的 subtype 。• 堅持「函式和資料應該被包裝在形式良好的單元內」。
– 如果你一直把本該在 extension class 的程式零散放在其他 classes 中,只會讓其他這些 classes 變得過分複雜,並使得其中函式難以被復用。
– local extension 有兩種作法• Subclass
– 必須在物件創建期實施。– 必須新增一個 class ,但是當原物件也被使用時,就要考慮維護兩份資料的問題。
• Wrapper– 通常首選 subclass ,因為工作量比較少。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 33
Introduce Local Extension (引入區域性擴展 )
• 作法– 建立一個 extension class ,將它作為原物 ( 原類別 ) 的 subclass 或
wrapper 。– 在 extension class 中加入轉型建構式 (converting constructors) 。
• Converting constructors: 接受原物作為參數– 在 extension class 中加入新特性。– 根據需要,將原物 (original) 替換為擴展物 (extension) 。– 將「針對原始類別而定義的所有外加函式 (foreign ethods) 」搬移到
extension class 中。
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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)
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 35
Introduce Local Extension (引入區域性擴展 )
• 範例 (Wrapper)class MfDateWrap{ private Date _original;
public MfDateWrap(String dateString) { _original = new Date(dateString); }
public MfDateWrap(Date arg) { _original = arg; }
// 為原始類別的所有函式提供委託函式… public int getYear() {return _original.getYear();} public int getMonth() {return _original.getMonth();} public int getDate() {return _original.getDate();} public boolean equals(MfDateWrap arg) {return (toDate().equals(arg.toDate()));}
Date nextDay() { return new Date(getYear(), getMonth(), getDate() + 1); }}
只是執行一個單純的委託動作(delegate)public boolean after (Date arg)
aWrapper.after(aDate) aWrapper.after(anotherWrapper)
aDate.after(aWrapper)
如何處理「接受原始類別之實體為參數」的函式?
Copyright 2009 Trend Micro Inc. 36
Introduce Local Extension (引入區域性擴展 )
• 範例 (Wrapper)– 上一頁” after” method 這種 overriding 的目的是為了向用戶隱藏
wrapper 的存在,這是一個好策略。
– 不過在某些系統所提供的函式 ( 如 equals) 會出問題。• public boolean equals (Date arg) // causes problems
– 這樣做是危險的,因為 Java 系統的其他部份都認為 equals符合交換律:如果 a.equals(b) 為真, b.equals(a) 也必為真。
– 違反這樣的規則將使我遭遇一大堆莫名其妙的錯誤,但是又無法同時修改 Date ,所以建議向用戶曝露「我進行了包裝」• public boolean equalsDate (Date arg)• public boolean equalsDate (MfDateWrap arg)
– Subclass則不會有這問題,只要不覆寫 original class 中的函式。• 一般來說不會在 extension class中覆寫 original class的函式,只會添加新函式。
Classification 04/10/2023
Copyright 2009 Trend Micro Inc.Classification 04/10/2023 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
決定把責任放在哪兒 ?
Copyright 2009 Trend Micro Inc. 38Classification 04/10/2023