返回總目錄 本小節目錄 Replace Method with Method Object(以函數對象取代函數) Substitute Algorithm(替換演算法) 階段性小結 Substitute Algorithm(替換演算法) 8 Replace Method with Method Obje ...
本小節目錄
8 Replace Method with Method Object(以函數對象取代函數)
概要
你有一個大型函數,其中對局部變數的使用使你無法採用Extract Method。
將這個函數放進一個單獨對象中,如此一來局部變數就成了對象內的欄位。然後你可以在同一個對象中將這個大型函數分解為多個小型函數。
動機
我們一直在強調,小型函數優美動人。只要將相對獨立的代碼從大型函數中提煉出來,就大大提高了函數的可讀性。
但是,局部變數的存在會增加函數分解難度。如果一個函數中局部變數泛濫成災,那麼這個時候Replace Temp with Query可以幫助你。有時候根本無法拆解一個需要拆解的函數,這時候Replace Method with Method Object就發揮作用了。
Replace Method with Method Object會將所有局部變數都變成函數對象的欄位。然後就可以對這個新函數使用Extract Method創造新函數,從而達到拆解的目的。
範例
如果要找到合適的例子,那麼需要很長的篇幅,所以我們杜撰了這樣一個函數。
class Account { int Gamma(int inputVal, int quantity, int yearToDate) { int importantValue1 = inputVal * quantity + Delta(); int importantValue2 = inputVal * yearToDate + 100; if (yearToDate - importantValue1 > 100) { importantValue2 -= 20; } int importantValue3 = importantValue2 * 7; //and so on... return importantValue3 - 2 * importantValue1; } public int Delta() { return 100; } }
為了把這個函數變成函數對象,首先聲明一個新類。在新類中,提供一個欄位用於保存原對象,同時也對函數的每個參數和每個臨時變數,提供欄位用於保存。
class Gamma { private readonly Account _account; private readonly int _inputVal; private readonly int _quantity; private readonly int _yearToDate; private int _importantValue1; private int _importantValue2; private int _importantValue3; }
接下來,加入一個構造函數:
public Gamma(Account account, int inputVal, int quantity, int yearToDate) { _account = account; _inputVal = inputVal; _quantity = quantity; _yearToDate = yearToDate; }
接下來,將原本的函數搬到Compute()中。
public int Compute() { _importantValue1 = _inputVal * _quantity + _account.Delta(); _importantValue2 = _inputVal * _yearToDate + 100; if (_yearToDate - _importantValue1 > 100) { _importantValue2 -= 20; } _importantValue3 = _importantValue2 * 7; //and so on... return _importantValue3 - 2 * _importantValue1; }
完整的Gamma函數如下:
class Gamma { private readonly Account _account; private readonly int _inputVal; private readonly int _quantity; private readonly int _yearToDate; private int _importantValue1; private int _importantValue2; private int _importantValue3;
public Gamma(Account account, int inputVal, int quantity, int yearToDate) { _account = account; _inputVal = inputVal; _quantity = quantity; _yearToDate = yearToDate; } public int Compute() { _importantValue1 = _inputVal * _quantity + _account.Delta(); _importantValue2 = _inputVal * _yearToDate + 100; if (_yearToDate - _importantValue1 > 100) { _importantValue2 -= 20; } _importantValue3 = _importantValue2 * 7; //and so on... return _importantValue3 - 2 * _importantValue1; } }
最後,修改舊函數,讓它的工作委托給剛完成的這個函數對象。
int Gamma(int inputVal, int quantity, int yearToDate) { return new Gamma(this, inputVal, quantity, yearToDate).Compute(); }
這就是本項重構的基本原則。它的好處是:現在我們可以輕鬆地對Compute()函數採取Extract Method,不必擔心參數傳遞的問題。
比如說我們對Compute進行如下重構:
public int Compute() { _importantValue1 = _inputVal * _quantity + _account.Delta(); _importantValue2 = _inputVal * _yearToDate + 100; GetImportantThing(); _importantValue3 = _importantValue2 * 7; //and so on... return _importantValue3 - 2 * _importantValue1; } void GetImportantThing() { if (_yearToDate - _importantValue1 > 100) { _importantValue2 -= 20; } }
小結
這種重構手法是針對大型函數,而且裡面的局部變數又有很多,那麼這個重構方法或許會讓你豁然開朗。
9 Substitute Algorithm(替換演算法)
概要
你想要把某個演算法替換為另外一個更清晰的演算法。
將函數本體替換為另一個演算法。
動機
我敢打賭,你從小到大,肯定做過這樣的題目:請使用兩種以上的解法來回答這道問題。這就是說明,某一道題肯定不只有一種解法,而且某些方法肯定會比另一些更簡單。演算法也是如此。
如果你發現做一件事可以有更清晰的方式,就應該以較清晰的方式取代較複雜的方式。隨著對問題有了更深入的瞭解,你往往會發現,在原先的做法之外,有更簡單的解決方案,此時,你要做的就是改變原先的演算法。
範例
我們以一個簡單的函數為例:
string FoundPerson(string[] people) { foreach (var person in people) { if (person == "Don") { return "Don"; } if (person == "John") { return "John"; } if (person == "Kent") { return "Kent"; } } return string.Empty; }
通過對這個演算法進行分析,我們發現此時如果用一個list集合,則函數會更簡單,更清晰。所以,重構之後,代碼如下:
string FoundPerson(string[] people) { var candidates = new List<string>() { "Don", "John", "Kent" }; foreach (var person in people) { if (candidates.Contains(person)) { return person; } } return string.Empty; }
小結
使用這項重構手法之前,請先確定自己已經儘可能分解了原先函數。替換一個巨大而複雜的演算法是非常困難的,只有先將它分解為較簡單的小型函數,然後你才能很有把握地進行演算法替換工作。
階段性小結
這幾小節的大標題叫做重新組織函數,顧名思義這些重構手法都是針對函數進行整理,使之更恰當地包裝代碼。幾乎所有時刻,問題都源於代碼的壞味道之Long Method(過長函數)。
對於過長函數,一項重要的重構手法就是Extract Method,它把一段代碼從原先函數中提取出來,放進獨立的函數中。而Inline Method則正好相反,它將一個函數調用替換為該函數本體。
提煉函數時最大的困難就是處理局部變數,其中一個便是臨時變數。處理一個函數時,可以先運用Replace Temp with Query去掉所有可能的臨時變數。如果多個地方使用了某個臨時變數,請先運用Split Temporary Variable將它變得比較容易替換。
如果臨時變數太混亂,難以替換。這時候Replace Method with Method Object就該登場了。
參數帶來的問題稍微少一些,前提是你不在函數內賦值給它們。如果你這樣做了,請使用Remove Assignments to Parameters。
函數分解完畢後,我就可以知道如何讓它工作得更好。也許某些演算法還可以改進,讓代碼更清晰。這就需要Substitute Algorithm來引入更清晰的演算法。
To Be Continued...