Fork me on GitHub

重构手法之简化函数调用【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……

posted @ 2017-12-01 08:51  NaYoung  阅读(968)  评论(0编辑  收藏  举报