重构手法之简化函数调用【2】
本小节目录
4Separate Query from Modifier(将查询函数和修改函数分离)
概要
某个函数既返回对象状态值,又修改对象状态(副作用)。
建立两个不同的函数,其中一个负责查询,另一个负责修改。
动机
任何有返回值的函数,都不应该有看得到的副作用。如果有一个函数“既有返回值又有副作用”,那么就应该尝试着将查询动作从修改动作中分割出来。
范例
有这样一个函数:一旦有人入侵安全系统,它会返回入侵者的姓名,并发送一个警报:
class Security { void CheckSecurity(string[] people) { string found = FoundMiscrent(people); } string FoundMiscrent(string[] people) { foreach (var person in people) { if (person == "Don") { SendAlert(); return "Don"; } if (person == "John") { SendAlert(); return "John"; } } return string.Empty; } void SendAlert() { } }
为了将查询动作和修改动作分开,首先建立一个适当的查询函数,使其与修改函数返回相同的值,但不造成任何副作用。
string FoundPerson(string[] people) { foreach (var person in people) { if (person == "Don") { return "Don"; } if (person == "John") { return "John"; } } return string.Empty; }
然后,逐一替换原函数内所有的return语句,该调用新建的查询函数:
string FoundMiscrent(string[] people) { foreach (var person in people) { if (person == "Don") { SendAlert(); return FoundPerson(people); } if (person == "John") { SendAlert(); return FoundPerson(people); } } return FoundPerson(people); }
现在修改调用者,将原本的单一调用者替换为两个调用:先调用修改函数,然后调用查询函数:
void CheckSecurity(string[] people) { FoundMiscrent(people); string found = FoundPerson(people); }
替换完毕后,将修改函数的返回值改为void,并修改其函数名称:
void SendAlert(string[] people) { foreach (var person in people { if (person == "Don") { SendAlert(); return; } if (person == "John") { SendAlert(); return; } } }
当然,这种情况下,得到了大量重复代码,因为修改函数之中使用了与查询函数相同的代码。现在对修改函数实施Substitute Algorithm,设法让它再简洁一些:
void SendAlert(string[] people) { if (FoundPerson(people) != "") { SendAlert(); } }
小结
如果某个函数只是向你提供一个值,没有任何看得到的副作用,那么你可以任意调用这个函数,也可以把调用动作搬移到函数的其他地方。简而言之,需要操心的事情少多了。
5Parameterize Method(令函数携带参数)
概要
若干函数做了类似的工作,但在函数本体中包含了不同的值。
建立单一函数,以参数表达那些不同的值。
动机
有这样两个函数:它们做着类似的工作,但因少数几个值致使行为略有不同。这时,可以将这些各自分离的函数统一起来,并通过参数来处理那些变化的情况,用以简化问题。
范例
先看一个简单的例子:
class Employee { public double Salary { get; set; } void TenPercentRaise() { Salary *= 1.1; } void FivePercentRaise() { Salary *= 1.05; } }
这段代码可以替换如下:
class Employee { public double Salary { get; set; }
void Raise(double factor) { Salary *= 1 + factor; } }
再来看一个稍微复杂的例子:
class Dollars { public Dollars(double result) { } } class Charge { private Dollars BaseCharge() { double result = Math.Min(LastUsage(), 100) * 0.03; if (LastUsage() > 100) { result += (Math.Min(LastUsage(), 200) - 100) * 0.05; } if (LastUsage() > 200) { result += (LastUsage() - 200) * 0.07; } return new Dollars(result); } private int LastUsage() { return 201; } }
上述代码可以替换如下:
class Charge { private Dollars BaseCharge() { double result = UsageInRange(0, 100) * 0.03; result += UsageInRange(100, 200) * 0.05; result += UsageInRange(200, Int32.MaxValue) * 0.07; return new Dollars(result); } private int UsageInRange(int start, int end) { if (LastUsage() > start) { return Math.Min(LastUsage(), end) - start; } return 0; } private int LastUsage() { return 201; } }
本项重构的要点在于:以“可将少量数值视为参数”为依据,找出带有重复性的代码。
小结
本项重构可以去除重复代码,并提高灵活性,因为可以用这个参数处理更多的变化情况。
To Be Continued……