Fork me on GitHub

重构手法之简化函数调用【4】

返回总目录

本小节目录

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

posted @ 2017-12-03 10:33  NaYoung  阅读(1050)  评论(0编辑  收藏  举报