重構手法之從新組織函數【5】

返回總目錄html

本小節目錄算法

8 Replace Method with Method Object(以函數對象取代函數)

概要

你有一個大型函數,其中對局部變量的使用使你沒法採用Extract Methodthis

將這個函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段。而後你能夠在同一個對象中將這個大型函數分解爲多個小型函數。spa

動機

咱們一直在強調,小型函數優美動人。只要將相對獨立的代碼從大型函數中提煉出來,就大大提升了函數的可讀性。code

可是,局部變量的存在會增長函數分解難度。若是一個函數中局部變量氾濫成災,那麼這個時候Replace Temp with Query能夠幫助你。有時候根本沒法拆解一個須要拆解的函數,這時候Replace Method with Method Object就發揮做用了。htm

Replace Method with Method Object會將全部局部變量都變成函數對象的字段。而後就能夠對這個新函數使用Extract Method創造新函數,從而達到拆解的目的。
對象

範例

若是要找到合適的例子,那麼須要很長的篇幅,因此咱們杜撰了這樣一個函數。blog

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;
       }
}

爲了把這個函數變成函數對象,首先聲明一個新類。在新類中,提供一個字段用於保存原對象,同時也對函數的每一個參數和每一個臨時變量,提供字段用於保存。get

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...

相關文章
相關標籤/搜索