概要 有些開發者在編寫方法時,可能較少地去思考一個問題:方法放在這個class中是否合適? 他們可能會覺得:這個方法已經實現xxx功能了,放在哪個class都一樣的,class不就是一個裝方法的容器嘛。 我贊同class是一個裝東西的容器,且不僅限於方法。 但是,容器是有區別的。本文要講的“移動方法... ...
概要
有些開發者在編寫方法時,可能較少地去思考一個問題:方法放在這個class中是否合適?
他們可能會覺得:這個方法已經實現xxx功能了,放在哪個class都一樣的,class不就是一個裝方法的容器嘛。
我贊同class是一個裝東西的容器,且不僅限於方法。
但是,容器是有區別的。本文要講的“移動方法”,是一種讓方法放進合適的class的重構策略。
選擇合適的容器
生活中我們會用到杯子和箱子,杯子和箱子都是容器。
倘若你用杯子裝書,用箱子裝水,會產生不好的結果——杯子里放不下書,水裝進箱子後,會打濕箱子。
// 杯子 public class Cup { // 裝書 public void HoldBook() { } } // 箱子 public class Box { // 裝水 public void HoldWater() { } }
按照生活常識,我們應該用杯子裝水,用箱子裝書。
// 杯子 public class Cup { // 裝水 public void HoldWater() { } } // 箱子 public class Box { // 裝書 public void HoldBook() { } }
每個程式員在開發完功能後,都應該回頭讀一讀自己的代碼,確認是否存在一些“牛頭不對馬嘴”的方法。
class是方法的容器,我們應該為每個方法尋找最合適的容器。
移動方法
現在引入本文的主題:“移動方法”。
當某個類的方法實現的功能更多地適用於另外一個類,且符合它的語義時,應將該方法移動到另外一個類。該定義中有兩個關鍵詞:1. 適用 2. 語義。
“適用性”是指方法實現的功能應該適用它的class,“語義”是指方法的所描述的功能和class的語義是一致的。
“語義”的重要性高於“適用性”,例如:在程式中定義的擴展方法或者幫助類,通常都是被其它class調用的。這種情況下,我們應該著重體現“語義”。
有些開發者在編寫Excel工具類時,在ExcelUtil中出定義了ExportCsv()這樣的方法,儘管導出的Csv格式是可以用Excel打開的。
較好的做法是,新建一個CsvUtil類,將ExportCsv()方法定義在其中,下圖闡述了這個重構過程(藍色表示重構前,紅色表示重構後)。
雖然多了一個class,但這使得Excel工具類和Csv工具類的語義更佳精確,代碼的可讀性也提高了。
示例
這段代碼定義了兩個類:BankAccount、AccountInterest,分別表示銀行賬戶和賬戶利率。
計算利率的方法CalculateInterestRate()
定義在BankAccount類。
namespace MoveMethod.Before { /// <summary> /// 銀行賬戶 /// </summary> public class BankAccount { public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest) { AccountAge = accountAge; CreditScore = creditScore; AccountInterest = accountInterest; } public int AccountAge { get; private set; } public int CreditScore { get; private set; } public AccountInterest AccountInterest { get; private set; } // 計算利率 public double CalculateInterestRate() { if (CreditScore > 800) return 0.02; if (AccountAge > 10) return 0.03; return 0.05; } } /// <summary> /// 賬戶利率 /// </summary> public class AccountInterest { public BankAccount Account { get; private set; } public AccountInterest(BankAccount account) { Account = account; } public double InterestRate { get { return Account.CalculateInterestRate(); } } public bool IntroductoryRate { get { return Account.CalculateInterestRate() < 0.05; } } } }
這兩個class用於描述一件客觀事實——”銀行賬戶和計算賬戶利率的方式“。
咋一看,這兩個類沒有什麼問題。
但由於已經定義了AccountInterest類,這個class的語義是和利率相關的,而CalculateInterestRate()
方法用於計算賬戶利率。
所以將CalculateInterestRate()
方法放在BankAccount類中不太合適,應將其移動到AccountInterest類中。
namespace MoveMethod.After { /// <summary> /// 銀行賬戶 /// </summary> public class BankAccount { public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest) { AccountAge = accountAge; CreditScore = creditScore; AccountInterest = accountInterest; } public int AccountAge { get; private set; } public int CreditScore { get; private set; } public AccountInterest AccountInterest { get; private set; } } /// <summary> /// 賬戶利率 /// </summary> public class AccountInterest { public BankAccount Account { get; private set; } public AccountInterest(BankAccount account) { Account = account; } public double InterestRate { get { return CalculateInterestRate(); } } public bool IntroductoryRate { get { return CalculateInterestRate() < 0.05; } } /// <summary> /// 計算利率 /// </summary> public double CalculateInterestRate() { if (Account.CreditScore > 800) return 0.02; if (Account.AccountAge > 10) return 0.03; return 0.05; } } }
下圖描述了重構前後的區別(藍色表示重構前,紅色表示重構後)。
總結
移動方法是較簡單的一種重構策略,它旨在將方法移動到合適的類。
這個策略的關鍵在於找到方法體現的行為(功能),以及該行為對應的語義。
找到語義,我們就能將它放到合適的類。
其中的難點也在於語義,語義應該具備“準確性”。
有時候業務知識、對現實物體(對象)的理解可能會阻礙我們尋找到準確的語義。