標籤:style blog http ar color 使用 sp strong on
明確函數所在類的位置是很重要的。這樣可以避免你的類與別的類有太多耦合。也會讓你的類的內聚性變得更加牢固,讓你的整個系統變得更加整潔。簡單來說,如果在你的程式中,某個類的函數在使用的過程中,更多的是在和別的類進行互動,調用後者或者被後者調用,那麼你就要注意了,你要去判斷這個類是否真正適合他原來所在的類。
簡單來說,這套手法就是在該函數最常引用的新類中建立一個有著類似行為的新函數,讓舊函數變成一個單純的委託函數或者完全刪掉。
Move Method是重構理論的支柱。如果一個類的責任太多,或者一個類和別的類有太多合作,耦合太高,都需要考慮Move Method讓類變得簡單點。你需要多瀏覽你自己寫的類,觀察類的職責是否太多,與別的類的耦合程度問題。當然,如果你進行了Move Field,你也應該做這樣的檢查,因為我們都知道,函數就跟欄位有關,欄位都被你移動了,函數肯定要再檢查一遍(如果同時需要Move Field和Move Method,一般先進行Move Filed會比較簡單)。一旦發現了這樣的函數,你要去分析他的調用端和被調用端,如果存在多態,你也應該去考慮繼承體系中他所重定義的函數,來判斷最終的移動路徑。
如果你不能肯定是否該移動一個函數,你就暫且別去考慮移動這個函數,你可以轉而去分析其他函數,移動其他函數往往會比你做這個決定來的簡單點。如果你發現你移動了其他函數之後你仍然無法判定是否要移動這個函數,那麼顯然此時的這個函數移動與不移動已經不重要了。
做法:
- 檢查源類中被源函數所使用的一切特性(欄位和函數),考慮它們是否應該也一起搬移。如果某個特性(函數或者欄位)只被你要移動的那個函數單獨用到,你可以一起搬移走。如果你發現在源類中其他函數也使用了這個特性,你應該考慮將使用這些特性的所有函數一併搬移走,有時候移動一組函數比一個一個搬移函數來的簡單。
- 檢查源類的子類和超類,看看是否有該函數的其他聲明。如果有其他聲明,說明這個函數是呈多態性的,除非你的目標類也呈現相同的繼承體系,不然你可能無法搬走。
- 在目標類聲明這個函數,介面名稱可以一樣也可以不一樣,由你根據語境把握。
- 將源函數的代碼複製到目標函數中,調整這些代碼,比如如果目標函數使用了源類中的特性(欄位和函數),你就得做出決定如何讓目標函數擷取到來源物件,如果目標類沒有相應的引用機制,你應該考慮是否增加參數表,把來源物件直接傳過去。如果你此時使用了異常,你也應該考慮異常應該放在源類中還是放在目標類中去。
- 編譯目標類。
- 決定如何從源函數去引用你建立的目標函數,如果存在一個現成的欄位或者函數可以讓你拿到目標對象那肯定最好,如果沒有,考慮是否可以建立一個這樣的取得目標類對象的函數,如果還是比較困難,你就該考慮是否應該給源類建立一個欄位用來專門儲存目標類對象,別去擔心這會不會是一個永久性修改,因為後期的重構項目可能會讓你把這些欄位給刪除掉。
- 修改源函數,讓他變成一個單純的委託函數去調用目標函數。
- 編譯,測試。
- 如果你經常在源類中引用目標函數,那麼你保留源函數介面讓他變成一個單純的委託函數顯然更容易一些。
- 如果你要刪除源函數,請將源類中對源函數的調用都替換為對目標函數的調用。你可以每修改一個點就編譯測試一次,你也可以利用通過你的編輯器進行批量修改然後進行編譯測試,這可能會更簡單點。
- 編譯,測試。
例子:
class Account...double overdraftCharge(){ if (m_type.isPremium()) { double result = 10; if (m_daysOverdrawn > 7) { result += (m_daysOverdrawn - 7) * 0.85; return result; } else { return m_daysOverdrawn * 1.75; } }}double bankCharge(){ double result = 4.5; if (m_daysOverdrawn > 0) { result += overdraftCharge(); return result; }}private: AccountType m_type; int m_daysOverdrawn;
我們發現會根據type的不同進行相應的不同運算規則,這提示我們應該把函數放到變化的類中去,即應該放到AccoountType中去。首先,我們觀察搬移函數,觀察其中使用的源類特性,發現只有m_daysOverdrawn是源類的特性,但我們不選擇將他搬移到目標類中而是放在源類中,因為我們可以斷定,他不會隨著賬戶的種類變化而變化,注意:這個技巧還是很重要的,將不變化的東西放到固定的類中,將容易變化的東西移動到相應的變化類中,可以讓我們後期重構做更好的多態處理。對於這次重構很簡單,我們可以直接以傳參的方式傳給目標函數。接下來,我們在AccountType中建立函數,並且命名,在這裡我們可以選擇同樣的名稱,然後把函數代碼全部貼過去,然後做相應的改變
class AccountType...double overdraftCharge(int daysOverdrawn){ if (isPremium()) { double result = 10; if (daysOverdrawn > 7) { result += (daysOverdrawn - 7) * 0.85; return result; } else { return daysOverdrawn * 1.75; } }}
把屬於自己的isPremium()變成直接調用,以參數的形式替代源類中自己的欄位。當我們需要源類的特性的時候其實有四種途徑
- 將這個特性也搬移到目標類中
- 建立或使用一個從目標類到源類的引用
- 將來源物件當作參數傳給目標函數
- 如果所需特性只是個變數,將它作為參數傳給目標函數
在這次例子中我們用了第四種方式,只是簡單的傳遞變數參數給目標函數。調整目標函數使之通過編譯之後我們進行源函數的修改,讓他變成一個簡單的委託,因為我們在源類中已經存在的AccountType的欄位,所以改起來很簡單,改完之後進行編譯測試。
class Account...double overdraftCharge(){ return m_type.overdraftCharge(m_daysOverdrawn);}
我們可以保留源函數現在這樣的樣子也可以刪除,如果你要刪除你就要多做一步,就是找到源函數的所有調用者,然後進行替換,比如Account中的bankCharge()函數用到了這個源函數就進行相應的替換,替換完成之後你就可以刪除源函數的聲明了。
double bankCharge(){ double result = 4.5; if (m_daysOverdrawn > 0) { result += m_type.overdraftCharge(m_daysOverdrawn); return result; }}
如果被搬移的函數不是private,你就必須檢查是否有其他類也使用了這個函數,因為C++是強型別語言,你可以簡單的刪除源函數的聲明,讓編譯器來幫你進行尋找。
因為在這個例子之中之用了源類的一個欄位,所以我們可以簡單的以傳參的形式給目標函數,如果源函數中使用了源類的別的函數或者多個欄位,我們就必須傳源類對象給目標函數
class AccountType...double overdraftCharge(Account *account){ if (isPremium()) { double result = 10; if (account->daysOverdrawn() > 7) { result += (account->daysOverdrawn() - 7) * 0.85; return result; } else { return account->daysOverdrawn() * 1.75; } }}
比如我們要擷取daysOverdrawn必須要通過源類的別的函數或者需要源類的多個特性,那麼此時我們就必須傳源類對象過去讓目標函數中進行調用。如果目標函數中需要源類的太多特性,那麼你就得進一步進行重構。通常,你需要Extract Method並將其中一部分移回源類。
『重構--改善既有代碼的設計』讀書筆記----Move Method