Classification 4/22/20111Refactoring Improving The Design of Existing CodeCh. 7 Moving Features Between ObjectsJoe C Wu  Developer, WSE(Web Services Engineering) Volume
OutlineCh. 7 Moving Features Between ObjectsMove Method (搬移函式)Move Field (搬移欄位)Extract Class (提練類別)Inline Class (將類別內聯化)Hide Delegate (隱藏「委託關係」)Remove Middle Man (移除中間人)Introduce Foreign Method (加入外加函式)Introduce Local Extension (引入區域性擴展)SummaryClassification 4/22/20112
Classification 4/22/20113Move Method (搬移函式)在該函式最常引用的class中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式(delegating method),或是將舊函式完全移除。
Classification 4/22/20114Move 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.很難決定 -> 或許移動這個函式與否,並不那麼重要。
Move Method (搬移函式)範例Classification 4/22/20115Class 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 overdraftChrgedouble bankCharge () {   double result = 4.5;   if (_daysOverdrawn > 0) result += overdraftCharge();   return result;} // end bankChargeprivate 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
Move Method (搬移函式)Classification 4/22/20116Class 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 overdraftChrgeClass Account…    double bankCharge () {   double result = 4.5;   if (_days|Overdrawn |> 0) result += _type.overdraftCharge(_daysOverdrawn);   return result;} // end bankCharge
Classification 4/22/20117Move Field (搬移欄位)某個class的field,被別的class使用更多次。在target class建立一個new field,修改source field的所有用戶,令它們改用new field.
Classification 4/22/20118Move 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.
Classification 4/22/20119Move Field (搬移欄位)範例Class Account…   private AccoutnType _type;   private double _interestRate;   double interestForAmount)days (double amount, int days)   {      return )interestRate * amount * days /365;   } // end interestForAmountWould like to move interestRate to the AccountType class because it is used more in that class.
Classification 4/22/201110Move 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_daysMove _interestRate.In the original classreference get and set methods for the field.
Classification 4/22/201111Extract Class (提練類別)某個class做了應該由兩個classes做的事。建立一個新的class,將相關的欄位和函式從舊class移至新class.
Classification 4/22/201112Extract Class (提練類別)動機一個class應該是一個清楚的抽象性(abstract),處理一些明確的責任。Class太大會不易理解。某些資料和函式總是一起出現、某些資料常同時變化並彼此相依,表示他們該被分離出去。作法決定如何分解class所負責任。建立新的class,將舊class的責任交給它。建立舊class與新class之間的link。Move Field -> TestMove Method -> Test檢查、精簡每個class的介面。決定是否讓新class曝光 => reference object / immutable value object
Classification 4/22/201113class 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;}Extract Class (提練類別)範例
Classification 4/22/201114Extract 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;}
Extract Class (提練類別)Move MethodClassification 4/22/201115class 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;}class Person{    public String getName(){        return _name;}    public String getTelephoneNumber(){        return _officeTelephone.getTelephoneNumber();}TelephoneNumbergetOfficeTelephone(){        return _officeTelephone;}    private String _name;    private TelephoneNumber _officeTelephone = new TelephoneNumber();}
Extract Class (提練類別)TelephoneNumberPublic?Private?Protected?若public新的class,要考慮別名(aliasing)的問題允許任何物件修改TelephoneNumber物件的任何部份使它成為Reference Object。(person為的存取點)  (Ch.8)不許任何人”不透過Person就修改它”設為immutable或是提供immutable interface。先複製一個TelephoneNumber物件,將這個物件傳給用戶,當然這也會造成一定程度的迷惑。你可以為提煉後的class加鎖(lock)。但要小心鎖定的問題。Classification 4/22/201116
Classification 4/22/201117Inline Class (將類別內聯化)你的某個class沒有做太多事情(沒有承擔足夠的責任)將class的所有特性搬移到另一個class中,然後移除原class.
Classification 4/22/201118Inline Class (將類別內聯化)動機如果一個class不再承擔足夠的責任,不再有單獨存在的理由。挑選最頻繁使用這個「萎縮class」的用戶,以Inline Class手法塞進去。作法在absorbing class(合併端class)宣告source class的public協定,並將所有函式委託(delegate)至source class.修改source class的引用點->absorbing class。Compile and TestMove Method and Move Field,將source class的特性移過去。幫source class舉行一個簡單的喪禮。
Classification 4/22/201119Inline Class (將類別內聯化)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;}範例class Person{    public String getName(){return _name;}    public String getTelephoneNumber(){return _officeTelephone.getTelephoneNumber();}TelephoneNumbergetOfficeTelephone(){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);}}
Classification 4/22/201120Inline Class (將類別內聯化)Person martin = new Person();martin.getOfficeTelephone().setAreaCode ("781");Person martin = new Person();martin.setAreaCode ("781");
Classification 4/22/201121Hide Delegate (隱藏「委託關係」)客戶直接呼叫其server object(服務物件)的delegate class。在Server端建立客戶所需的所有函式,用以隱藏委托關係(delegation)。
Classification 4/22/201122Hide Delegate (隱藏「委託關係」)動機封裝即使不是物件的最關鍵特徵,也是最關鍵特徵之一。如果某個客戶呼叫了「建立於server object的某個欄位基礎之上」的函式,客戶就必需知道這個委託物件(delegate object),萬一委託關係發生變化,客戶也得相應變化。將委託關係隱藏起來,去除這種依存性,將變化限制在server中,不會波及客戶。
Classification 4/22/201123Hide Delegate (隱藏「委託關係」)作法對每一個委託關係中的函式,在server端建立一個簡單的委託函式(delegating method)。調整客戶,令它只呼叫server提供的函式Compile and Test如果將來不再有任何客戶需要用到Delegate,便可移除server中的存取函式Compile and Test
Classification 4/22/201124Hide 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();
Classification 4/22/201125Remove Middle Man (移除中間人)某個class做了過多的簡單委託動作(simple delegation)。讓客戶直接呼叫delegate(受託類別)。
Classification 4/22/201126Remove Middle Man (移除中間人)動機Hide Delegate的代價當客戶要用到delegate的新特性時,你就必須在server端添加一個簡單委託函式。delegate的特性(功能)愈來愈多,就愈來愈痛苦。很難說什麼程度的隱藏才是合適的。隨著系統的變化,改成合適的就好了。重構的意義就在於:你永遠不必說什麼對不起,只要把出問題的地方修補好就行了。作法建立一個函式,用以取代delegate(受托物件)。對於每個委託函式(delegate method),在server中刪除該函式,並將「客戶對該函式的呼叫」替換為「對delegate的呼叫」。Compile and Test
Classification 4/22/201127Remove 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();
Classification 4/22/201128Introduce 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);}
Classification 4/22/201129Introduce Foreign Method (加入外加函式)動機你正在使用一個class,它真的很好,為你提供了你想要的所有服務。當你需要一個新服務,這個class卻無法供應…如果可以修改源碼,你便可以加一個新函式,如果不能,你就得在客戶端編碼,補足你要的函式。如果你需要使用很多次,你就得不斷重初這些程式碼…如果你以外加函式實現一個新功能,那就是一個明確信號:這個函式原本應該在提供服務的class中加以實現。如果一個server class加入了大量的外加函式,你就不應該再使用本項重構,而應該使用Introduce Local Extension。
Classification 4/22/201130Introduce 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);}
Classification 4/22/201131Introduce Local Extension (引入區域性擴展)你所使用的server class需要一些額外函式,但你無法修改這個class。建立一個新class,使用包含這些額外函式。讓這個擴展品成盥source class的sub class(子類別)或wrapper(外覆類別)。
Classification 4/22/201132Introduce Local Extension (引入區域性擴展)動機如果只需要一兩個函式,可以使用Introduce Foreign Method。超果兩個,可以用這個方法將這些函式組織在一起,這種方式稱之為local extension(區域性擴展)是一個獨立的class,卻也是其extended class的subtype。堅持「函式和資料應該被包裝在形式良好的單元內」。如果你一直把本該在extension class的程式零散放在其他classes中,只會讓其他這些classes變得過分複雜,並使得其中函式難以被復用。local extension有兩種作法Subclass必須在物件創建期實施。必須新增一個class,但是當原物件也被使用時,就要考慮維護兩份資料的問題。Wrapper通常首選subclass,因為工作量比較少。
Classification 4/22/201133Introduce Local Extension (引入區域性擴展)作法建立一個extension class,將它作為原物(原類別)的subclass或wrapper。在extension class中加入轉型建構式(converting constructors)。Converting constructors: 接受原物作為參數在extension class中加入新特性。根據需要,將原物(original)替換為擴展物(extension)。將「針對原始類別而定義的所有外加函式(foreign ethods)」搬移到extension class中。
Classification 4/22/201134Introduce 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)
Classification 4/22/201135Introduce 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)
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/201136
Classification 4/22/201137Summary決定把責任放在哪兒?重構的基本手法Move Field > Move MethodClass責任 過多 或是 過少Extract Class V.S Inline ClassClass使用另一ClassHide Delegate將關係隱藏起來因為Hide delegate導致擁有者的介面經常變化Remove Middle Man當我不能存取某個class的源碼,又想把其他責任移進去Introduce Foreign Method (只加一、二個函式)Introduce Local Extension
Classification 4/22/201138

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

  • 1.
    Classification 4/22/20111Refactoring ImprovingThe Design of Existing CodeCh. 7 Moving Features Between ObjectsJoe C Wu Developer, WSE(Web Services Engineering) Volume
  • 2.
    OutlineCh. 7 MovingFeatures Between ObjectsMove Method (搬移函式)Move Field (搬移欄位)Extract Class (提練類別)Inline Class (將類別內聯化)Hide Delegate (隱藏「委託關係」)Remove Middle Man (移除中間人)Introduce Foreign Method (加入外加函式)Introduce Local Extension (引入區域性擴展)SummaryClassification 4/22/20112
  • 3.
    Classification 4/22/20113Move Method(搬移函式)在該函式最常引用的class中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式(delegating method),或是將舊函式完全移除。
  • 4.
    Classification 4/22/20114Move 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.很難決定 -> 或許移動這個函式與否,並不那麼重要。
  • 5.
    Move Method (搬移函式)範例Classification4/22/20115Class 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 overdraftChrgedouble bankCharge () { double result = 4.5; if (_daysOverdrawn > 0) result += overdraftCharge(); return result;} // end bankChargeprivate 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 (搬移函式)Classification4/22/20116Class 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 overdraftChrgeClass Account… double bankCharge () { double result = 4.5; if (_days|Overdrawn |> 0) result += _type.overdraftCharge(_daysOverdrawn); return result;} // end bankCharge
  • 7.
    Classification 4/22/20117Move Field(搬移欄位)某個class的field,被別的class使用更多次。在target class建立一個new field,修改source field的所有用戶,令它們改用new field.
  • 8.
    Classification 4/22/20118Move 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/20119Move Field(搬移欄位)範例Class Account… private AccoutnType _type; private double _interestRate; double interestForAmount)days (double amount, int days) { return )interestRate * amount * days /365; } // end interestForAmountWould like to move interestRate to the AccountType class because it is used more in that class.
  • 10.
    Classification 4/22/201110Move 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_daysMove _interestRate.In the original classreference get and set methods for the field.
  • 11.
    Classification 4/22/201111Extract Class(提練類別)某個class做了應該由兩個classes做的事。建立一個新的class,將相關的欄位和函式從舊class移至新class.
  • 12.
    Classification 4/22/201112Extract Class(提練類別)動機一個class應該是一個清楚的抽象性(abstract),處理一些明確的責任。Class太大會不易理解。某些資料和函式總是一起出現、某些資料常同時變化並彼此相依,表示他們該被分離出去。作法決定如何分解class所負責任。建立新的class,將舊class的責任交給它。建立舊class與新class之間的link。Move Field -> TestMove Method -> Test檢查、精簡每個class的介面。決定是否讓新class曝光 => reference object / immutable value object
  • 13.
    Classification 4/22/201113class 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;}Extract Class (提練類別)範例
  • 14.
    Classification 4/22/201114Extract 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;}
  • 15.
    Extract Class (提練類別)MoveMethodClassification 4/22/201115class 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;}class Person{ public String getName(){ return _name;} public String getTelephoneNumber(){ return _officeTelephone.getTelephoneNumber();}TelephoneNumbergetOfficeTelephone(){ return _officeTelephone;} private String _name; private TelephoneNumber _officeTelephone = new TelephoneNumber();}
  • 16.
    Extract Class (提練類別)TelephoneNumberPublic?Private?Protected?若public新的class,要考慮別名(aliasing)的問題允許任何物件修改TelephoneNumber物件的任何部份使它成為ReferenceObject。(person為的存取點) (Ch.8)不許任何人”不透過Person就修改它”設為immutable或是提供immutable interface。先複製一個TelephoneNumber物件,將這個物件傳給用戶,當然這也會造成一定程度的迷惑。你可以為提煉後的class加鎖(lock)。但要小心鎖定的問題。Classification 4/22/201116
  • 17.
    Classification 4/22/201117Inline Class(將類別內聯化)你的某個class沒有做太多事情(沒有承擔足夠的責任)將class的所有特性搬移到另一個class中,然後移除原class.
  • 18.
    Classification 4/22/201118Inline Class(將類別內聯化)動機如果一個class不再承擔足夠的責任,不再有單獨存在的理由。挑選最頻繁使用這個「萎縮class」的用戶,以Inline Class手法塞進去。作法在absorbing class(合併端class)宣告source class的public協定,並將所有函式委託(delegate)至source class.修改source class的引用點->absorbing class。Compile and TestMove Method and Move Field,將source class的特性移過去。幫source class舉行一個簡單的喪禮。
  • 19.
    Classification 4/22/201119Inline Class(將類別內聯化)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;}範例class Person{ public String getName(){return _name;} public String getTelephoneNumber(){return _officeTelephone.getTelephoneNumber();}TelephoneNumbergetOfficeTelephone(){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);}}
  • 20.
    Classification 4/22/201120Inline Class(將類別內聯化)Person martin = new Person();martin.getOfficeTelephone().setAreaCode ("781");Person martin = new Person();martin.setAreaCode ("781");
  • 21.
    Classification 4/22/201121Hide Delegate(隱藏「委託關係」)客戶直接呼叫其server object(服務物件)的delegate class。在Server端建立客戶所需的所有函式,用以隱藏委托關係(delegation)。
  • 22.
    Classification 4/22/201122Hide Delegate(隱藏「委託關係」)動機封裝即使不是物件的最關鍵特徵,也是最關鍵特徵之一。如果某個客戶呼叫了「建立於server object的某個欄位基礎之上」的函式,客戶就必需知道這個委託物件(delegate object),萬一委託關係發生變化,客戶也得相應變化。將委託關係隱藏起來,去除這種依存性,將變化限制在server中,不會波及客戶。
  • 23.
    Classification 4/22/201123Hide Delegate(隱藏「委託關係」)作法對每一個委託關係中的函式,在server端建立一個簡單的委託函式(delegating method)。調整客戶,令它只呼叫server提供的函式Compile and Test如果將來不再有任何客戶需要用到Delegate,便可移除server中的存取函式Compile and Test
  • 24.
    Classification 4/22/201124Hide 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();
  • 25.
    Classification 4/22/201125Remove MiddleMan (移除中間人)某個class做了過多的簡單委託動作(simple delegation)。讓客戶直接呼叫delegate(受託類別)。
  • 26.
    Classification 4/22/201126Remove MiddleMan (移除中間人)動機Hide Delegate的代價當客戶要用到delegate的新特性時,你就必須在server端添加一個簡單委託函式。delegate的特性(功能)愈來愈多,就愈來愈痛苦。很難說什麼程度的隱藏才是合適的。隨著系統的變化,改成合適的就好了。重構的意義就在於:你永遠不必說什麼對不起,只要把出問題的地方修補好就行了。作法建立一個函式,用以取代delegate(受托物件)。對於每個委託函式(delegate method),在server中刪除該函式,並將「客戶對該函式的呼叫」替換為「對delegate的呼叫」。Compile and Test
  • 27.
    Classification 4/22/201127Remove MiddleMan (移除中間人)範例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/201128Introduce ForeignMethod (加入外加函式)你所使用的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);}
  • 29.
    Classification 4/22/201129Introduce ForeignMethod (加入外加函式)動機你正在使用一個class,它真的很好,為你提供了你想要的所有服務。當你需要一個新服務,這個class卻無法供應…如果可以修改源碼,你便可以加一個新函式,如果不能,你就得在客戶端編碼,補足你要的函式。如果你需要使用很多次,你就得不斷重初這些程式碼…如果你以外加函式實現一個新功能,那就是一個明確信號:這個函式原本應該在提供服務的class中加以實現。如果一個server class加入了大量的外加函式,你就不應該再使用本項重構,而應該使用Introduce Local Extension。
  • 30.
    Classification 4/22/201130Introduce ForeignMethod (加入外加函式)作法在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);}
  • 31.
    Classification 4/22/201131Introduce LocalExtension (引入區域性擴展)你所使用的server class需要一些額外函式,但你無法修改這個class。建立一個新class,使用包含這些額外函式。讓這個擴展品成盥source class的sub class(子類別)或wrapper(外覆類別)。
  • 32.
    Classification 4/22/201132Introduce LocalExtension (引入區域性擴展)動機如果只需要一兩個函式,可以使用Introduce Foreign Method。超果兩個,可以用這個方法將這些函式組織在一起,這種方式稱之為local extension(區域性擴展)是一個獨立的class,卻也是其extended class的subtype。堅持「函式和資料應該被包裝在形式良好的單元內」。如果你一直把本該在extension class的程式零散放在其他classes中,只會讓其他這些classes變得過分複雜,並使得其中函式難以被復用。local extension有兩種作法Subclass必須在物件創建期實施。必須新增一個class,但是當原物件也被使用時,就要考慮維護兩份資料的問題。Wrapper通常首選subclass,因為工作量比較少。
  • 33.
    Classification 4/22/201133Introduce LocalExtension (引入區域性擴展)作法建立一個extension class,將它作為原物(原類別)的subclass或wrapper。在extension class中加入轉型建構式(converting constructors)。Converting constructors: 接受原物作為參數在extension class中加入新特性。根據需要,將原物(original)替換為擴展物(extension)。將「針對原始類別而定義的所有外加函式(foreign ethods)」搬移到extension class中。
  • 34.
    Classification 4/22/201134Introduce LocalExtension (引入區域性擴展)範例(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/201135Introduce LocalExtension (引入區域性擴展)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/201136
  • 37.
    Classification 4/22/201137Summary決定把責任放在哪兒?重構的基本手法Move Field> Move MethodClass責任 過多 或是 過少Extract Class V.S Inline ClassClass使用另一ClassHide Delegate將關係隱藏起來因為Hide delegate導致擁有者的介面經常變化Remove Middle Man當我不能存取某個class的源碼,又想把其他責任移進去Introduce Foreign Method (只加一、二個函式)Introduce Local Extension
  • 38.