您的位置:首页 > 其它

重构手法之重新组织函数【5】

2017-11-21 07:58 288 查看

返回总目录

本小节目录

8 Replace Method with Method Object(以函数对象取代函数)

概要

你有一个大型函数,其中对局部变量的使用使你无法采用Extract Method

将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。

动机

我们一直在强调,小型函数优美动人。只要将相对独立的代码从大型函数中提炼出来,就大大提高了函数的可读性。

但是,局部变量的存在会增加函数分解难度。如果一个函数中局部变量泛滥成灾,那么这个时候Replace Temp with Query可以帮助你。有时候根本无法拆解一个需要拆解的函数,这时候Replace Method with Method Object就发挥作用了。

Replace Method with Method Object会将所有局部变量都变成函数对象的字段。然后就可以对这个新函数使用Extract Method创造新函数,从而达到拆解的目的。

范例

如果要找到合适的例子,那么需要很长的篇幅,所以我们杜撰了这样一个函数。

class Account
{
int Gamma(int inputVal, int quantity, int yearToDate)
{
int importantValue1 = inputVal * quantity + Delta();
int importantValue2 = inputVal * yearToDate + 100;
if (yearToDate - importantValue1 > 100)
{
importantValue2 -= 20;
}
int importantValue3 = importantValue2 * 7;
//and so on...
return importantValue3 - 2 * importantValue1;
}
public int Delta()
{
return 100;
}
}

为了把这个函数变成函数对象,首先声明一个新类。在新类中,提供一个字段用于保存原对象,同时也对函数的每个参数和每个临时变量,提供字段用于保存。

class Gamma
{

private readonly Account _account;

private readonly int _inputVal;

private readonly int _quantity;

private readonly int _yearToDate;

private int _importantValue1;

private int _importantValue2;

private int _importantValue3;
}

接下来,加入一个构造函数:

public Gamma(Account account, int inputVal, int quantity, int yearToDate)
{
_account = account;
_inputVal = inputVal;
_quantity = quantity;
_yearToDate = yearToDate;
}

接下来,将原本的函数搬到Compute()中。

public int Compute()
{
_importantValue1 = _inputVal * _quantity + _account.Delta();
_importantValue2 = _inputVal * _yearToDate + 100;
if (_yearToDate - _importantValue1 > 100)
{
_importantValue2 -= 20;
}
_importantValue3 = _importantValue2 * 7;
//and so on...
return _importantValue3 - 2 * _importantValue1;
}

完整的Gamma函数如下:

class Gamma
{

private readonly Account _account;

private readonly int _inputVal;

private readonly int _quantity;

private readonly int _yearToDate;

private int _importantValue1;

private int _importantValue2;

private int _importantValue3;
public Gamma(Account account, int inputVal, int quantity, int yearToDate) { _account = account; _inputVal = inputVal; _quantity = quantity; _yearToDate = yearToDate; } public int Compute() { _importantValue1 = _inputVal * _quantity + _account.Delta(); _importantValue2 = _inputVal * _yearToDate + 100; if (_yearToDate - _importantValue1 > 100) { _importantValue2 -= 20; } _importantValue3 = _importantValue2 * 7; //and so on... return _importantValue3 - 2 * _importantValue1; } }

最后,修改旧函数,让它的工作委托给刚完成的这个函数对象。

int Gamma(int inputVal, int quantity, int yearToDate)
{
return new Gamma(this, inputVal, quantity, yearToDate).Compute();
}

这就是本项重构的基本原则。它的好处是:现在我们可以轻松地对Compute()函数采取Extract Method,不必担心参数传递的问题。

比如说我们对Compute进行如下重构:

public int Compute()
{
_importantValue1 = _inputVal * _quantity + _account.Delta();
_importantValue2 = _inputVal * _yearToDate + 100;
GetImportantThing();
_importantValue3 = _importantValue2 * 7;
//and so on...
return _importantValue3 - 2 * _importantValue1;
}

void GetImportantThing()
{
if (_yearToDate - _importantValue1 > 100)
{
_importantValue2 -= 20;
}
}

小结

这种重构手法是针对大型函数,而且里面的局部变量又有很多,那么这个重构方法或许会让你豁然开朗。

9 Substitute Algorithm(替换算法)

概要

你想要把某个算法替换为另外一个更清晰的算法。

将函数本体替换为另一个算法。

动机

我敢打赌,你从小到大,肯定做过这样的题目:请使用两种以上的解法来回答这道问题。这就是说明,某一道题肯定不只有一种解法,而且某些方法肯定会比另一些更简单。算法也是如此。

如果你发现做一件事可以有更清晰的方式,就应该以较清晰的方式取代较复杂的方式。随着对问题有了更深入的了解,你往往会发现,在原先的做法之外,有更简单的解决方案,此时,你要做的就是改变原先的算法。

范例

我们以一个简单的函数为例:

string FoundPerson(string[] people)
{
foreach (var person in people)
{
if (person == "Don")
{
return "Don";
}
if (person == "John")
{
return "John";
}
if (person == "Kent")
{
return "Kent";
}
}
return string.Empty;
}

通过对这个算法进行分析,我们发现此时如果用一个list集合,则函数会更简单,更清晰。所以,重构之后,代码如下:

string FoundPerson(string[] people)
{
var candidates = new List<string>() { "Don", "John", "Kent" };
foreach (var person in people)
{
if (candidates.Contains(person))
{
return person;
}
}
return string.Empty;
}

小结

使用这项重构手法之前,请先确定自己已经尽可能分解了原先函数。替换一个巨大而复杂的算法是非常困难的,只有先将它分解为较简单的小型函数,然后你才能很有把握地进行算法替换工作。

阶段性小结

这几小节的大标题叫做重新组织函数,顾名思义这些重构手法都是针对函数进行整理,使之更恰当地包装代码。几乎所有时刻,问题都源于代码的坏味道之Long Method(过长函数)。

对于过长函数,一项重要的重构手法就是Extract Method,它把一段代码从原先函数中提取出来,放进独立的函数中。而Inline Method则正好相反,它将一个函数调用替换为该函数本体。

提炼函数时最大的困难就是处理局部变量,其中一个便是临时变量。处理一个函数时,可以先运用Replace Temp with Query去掉所有可能的临时变量。如果多个地方使用了某个临时变量,请先运用Split Temporary Variable将它变得比较容易替换。

如果临时变量太混乱,难以替换。这时候Replace Method with Method Object就该登场了。

参数带来的问题稍微少一些,前提是你不在函数内赋值给它们。如果你这样做了,请使用Remove Assignments to Parameters

函数分解完毕后,我就可以知道如何让它工作得更好。也许某些算法还可以改进,让代码更清晰。这就需要Substitute Algorithm来引入更清晰的算法。

 

To Be Continued...

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: