1. 程式人生 > >重構手法之簡化函數調用【4】

重構手法之簡化函數調用【4】

希望 重復 範圍對 href idt 移植 參數 不同的 lis

返回總目錄

本小節目錄

  • Replace Parameter with Methods(以函數取代參數)
  • Introduce Parameter Object(引入參數對象)
  • Remove Setting Method(移除設值函數)

8Replace Parameter with Methods(以函數取代參數)

概要

對象調用某個函數,並將所得結果做為參數,傳遞給另一個函數。而接受參數的函數本身也能夠調用前一個函數。

讓參數接受者去除該項參數,並直接調用前一個函數

動機

如果函數通過其他途徑獲得參數值,那麽它就不應該通過參數取得該值。過長的參數列會增加程序閱讀者的理解難度,因此我們應該盡可能的縮短參數列的長度。

一種縮減參數列的辦法:看看“參數接受端”是否可以通過“與調用端相同的計算”來取得參數值。如果調用端通過其所屬對象內部的另一個函數來計算參數,並在計算過程中“未曾引用調用端的其他參數”,那麽就可以將這個計算過程轉移到被調用端內,從而去除該項參數。

範例

以下代碼用於計算定單折扣價格。

class Price
{
    public int Quantity { get; set; }

    public int ItemPrice { get; set; }
    public double GetPrice()
    {
        
int basePrice = Quantity * ItemPrice; int discountLevel = Quantity > 100 ? 2 : 1; double finalPrice = GetDiscountedPrice(basePrice, discountLevel); return finalPrice; } private double GetDiscountedPrice(int basePrice, int discountLevel) { if (discountLevel == 2
) { return basePrice * 0.1; } return basePrice * 0.05; } }

首先,把計算折扣等級(discountLevel)的代碼提煉成一個獨立的函數:

private int GetDiscountLevel()
{
    return Quantity > 100 ? 2 : 1;
}

接下來替換所有的引用點,並使用Remove parameter去掉參數:

class Price
{
    public int Quantity { get; set; }

    public int ItemPrice { get; set; }
    public double GetPrice()
    {
        int basePrice = Quantity * ItemPrice;
        double finalPrice = GetDiscountedPrice(basePrice);
        return finalPrice;
    }

    private int GetDiscountLevel()
    {
        return Quantity > 100 ? 2 : 1;
    }
    private double GetDiscountedPrice(int basePrice)
    {
        if (GetDiscountLevel() == 2)
        {
            return basePrice * 0.1;
        }
        return basePrice * 0.05;
    }
}

使用相同方法,去除basePrice:

class Price
{
    public int Quantity { get; set; }

    public int ItemPrice { get; set; }
    public double GetPrice()
    {
        return GetDiscountedPrice();
    }
    private int GetBasePrice()
    {
        return Quantity * ItemPrice;
    }
    private int GetDiscountLevel()
    {
        return Quantity > 100 ? 2 : 1;
    }
    private double GetDiscountedPrice()
    {
        if (GetDiscountLevel() == 2)
        {
            return GetBasePrice() * 0.1;
        }
        return GetBasePrice() * 0.05;
    }
}

最後觀察發現,可以對GetDiscountedPrice()函數使用Inline Method:

class Price
{
    public int Quantity { get; set; }

    public int ItemPrice { get; set; }
    public double GetPrice()
    {
        if (GetDiscountLevel() == 2)
        {
            return GetBasePrice() * 0.1
        }
        return GetBasePrice() * 0.05;
    }
    private int GetBasePrice()
    {
        return Quantity * ItemPrice;
    }
    private int GetDiscountLevel()
    {
        return Quantity > 100 ? 2 : 1;
    }
  
}

小結

使用該重構手法可以縮減參數列,使程序更容易理解。

9Introduce Parameter Object(引入參數對象)

概要

某些參數總是很自然地同時出現。以一個對象取代這些參數

動機

一組參數可能有幾個函數同時使用,這些函數可能隸屬於同一個類,也可能隸屬於不同的類。這樣的一組參數就是所謂的Data Clump(數據泥團),我們可以運用一個對象包裝所有這些數據,再以對象取代它們。本項重構的價值在於縮短參數列。此外,新對象所定義的訪問函數還可以使代碼更具一致性,這又進一步降低了代碼的理解和修改難度。

範例

/// <summary>
/// 賬項類
/// </summary>
class Entry
{
    public DateTime ChargeDate { get; set; }

    public double Value { get; set; }

    public Entry(double value, DateTime chargeDate)
    {
        Value = value;
        ChargeDate = chargeDate;
    }
}
/// <summary>
/// 賬目類
/// </summary>
class Account
{
    private List<Entry> _entries = new List<Entry>();
    /// <summary>
    /// 計算兩個日期之間的賬項總量
    /// </summary>
    /// <param name="start">開始日期</param>
    /// <param name="end">結束日期</param>
    /// <returns></returns>
    public double GetFlowBetween(DateTime start, DateTime end)
    {
        return _entries.Where(entry => entry.ChargeDate >= start && entry.ChargeDate <= end).Sum(entry => entry.Value);
    }
}

現在客戶端要調用的話,可能代碼如下:

Account anAccount = new Account();
anAccount.GetFlowBetween(DateTime.Now, DateTime.Now.AddMonths(1));

我們現在以“範圍對象”來取而代之。首先在Account類新建一個簡單的數據容器,用以表示範圍:

class DateRange
{
    public DateTime Start { get; }

    public DateTime End { get; }

    public DateRange(DateTime start, DateTime end)
    {
        Start = start;
        End = end;
    }
}

然後修改GetFlowBetween()函數的參數:

/// <summary>
/// 計算兩個日期之間的賬項總量
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
public double GetFlowBetween(DateRange range)
{
    return _entries.Where(entry => entry.ChargeDate >= range.Start && entry.ChargeDate <= range.End).Sum(entry => entry.Value);
}

現在客戶端調用的代碼可能變成這樣:

Account anAccount = new Account();
anAccount.GetFlowBetween(new DateRange(DateTime.Now, DateTime.Now.AddMonths(1)));

小結

技術分享圖片

本項重構還可以帶來更多的好處。當把這些參數組織到一起之後,往往很快可以發現可被移植新建類的行為。通常,將原本使用那些參數的函數對這一組參數會有一些共通的處理,如果將這些共同行為移動新對象中,可以減少重復代碼。

10Remove Setting Method(移除設值函數)

概要

類中的某個字段或者屬性應該在對象創建時被設值,然後就不再改變。

去掉該字段或者屬性的所有設值函數。

動機

如果你為某個字段或者屬性提供了設值函數,這就暗示了這個字段或者屬性值可以被改變。如果你不希望在對象初創之後,此字段或者屬性還有機會改變,那就不要為它提供設值函數。這樣你的意圖會更加清晰,並且可以排除其值被修改的可能性。

範例

看一個簡單的例子:

class Account
{
    public string Id{ get; set; }

    public Account(string id)
    {
        this.Id ="ZZ"+id;
    }
}

以上代碼可以修改為:

class Account
{
    public string Id { get; }

    public Account(string id)
    {
        this.Id = "ZZ"+id;
    }
}

To Be Continued……

重構手法之簡化函數調用【4】