重构手法之简化函数调用【4】
本小节目录
- 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……