🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### Replace Method with Method Object(以函数对象取代函数) 你有一个大型函数,其中对局部变量的使用,使你无法釆用 Extract Method。 将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型函数分解为数个小型函数。 ~~~ class Order... double price() { double primaryBasePrice; double secondaryBasePrice; double tertiaryBasePrice; // long computation; ... } ~~~ ![](https://box.kancloud.cn/2016-08-15_57b1b56bc94e7.gif) **动机(Motivation)** 我在本书中不断向读者强调小型函数的优美动人。只要将相对独立的代码从大型函数中提炼出来,就可以大大提高代码的可读性。 但是,局部变量的存在会增加函数分解难度。如果一个函数之中局部变量泛滥成灾, 那么想分解这个函数是非常困难的。Replace Temp with Query 可以助你减轻这一负担,但有时候你会发现根本无法拆解一个需要拆解的函数。这种情况下,你应该把手深深地伸入你的工具箱(好酒沉瓮底呢),祭出函数对象(method object )[Beck]这件法宝。 Replace Method with Method Object 会将所有局部变量都变成函数对象(method object)的值域(field)。然后你就可以对这个新对象使用 Extract Method 创造出新函数,从而将原本的大型函数拆解变短。 **作法(Mechanics)** 我厚着脸皮从Kent Beck [Beck]那里偷来了下列作法: - 建立一个新class,根据「待被处理之函数」的用途,为这个class命名。 - 在新class中建立一个final值域,用以保存原先大型函数所驻对象。我们将这个值域称为「源对象」。同时,针对原(旧)函数的每个临时变量和每个参 数,在新中建立一个个对应的值域保存之。 - 在新class中建立一个构造函数(constructor),接收源对象及原函数的所有参数作为参数。 - 在新class中建立一个compute()函数。 - 将原(旧)函数的代码拷贝到compute()函数中。如果需要调用源对象的任何函数,请以「源对象」值域调用。 - 编译。 - 将旧函数的函数本体替换为这样一条语句:「创建上述新的一个新对象, 而后调用其中的compute()函数」。 现在进行到很有趣的部分了。由于所有局部变量现在都成了值域,所以你可以任意分解这个大型函数,不必传递任何参数。 **范例(Example)** 如果要给这一重构手法找个合适例子,需要很长的篇幅。所以我以一个不需要长篇幅(那也就是说可能不十分完美)的例子展示这项重构。请不要问这个函数的逻辑是什么,这完全是我且战且走的产品。 ~~~ 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; } ~~~ 为了把这个函数变成一个函数对象(method object),我首先需要声明一个新class。在此新class中我应该提供一个final值域用以保存原先对象(源对象);对于函数的每一个参数和每一个临时变量,也以一个个值域逐一保存。 ~~~ class Gamma... private final Account _account; private int inputVal; private int quantity; private int yearToDate; private int importantValue1; private int importantValue2; private int importantValue3; ~~~ 按惯例,我通常会以下划线作为值域名称的前缀。但为了保持小步前进,我暂时先保留这些值域的原名。 接下来,加入一个构造函数: ~~~ Gamma (Account source, int inputValArg, int quantityArg, int yearToDateArg) { _account = source; inputVal = inputValArg; quantity = quantityArg; yearToDate = yearToDateArg; } ~~~ 现在可以把原本的函数搬到compute()了。函数中任何调用Account class的地方,我都必须改而使用_account值域: ~~~ int compute () { importantValue1 = (inputVal * quantity) + _account.delta(); importantValue2 = (inputVal * yearToDate) + 100; if ((yearToDate - importantValue1) > 100) importantValue2 -= 20; int importantValue3 = importantValue2 * 7; // and so on. return importantValue3 - 2 * importantValue1; } ~~~ 然后,我修改旧函数,让它将它的工作转发〔委托,delegate)给刚完成的这个函 数对象(method object): ~~~ int gamma (int inputVal, int quantity, int yearToDate) { return new Gamma(this, inputVal, quantity, yearToDate).compute(); } ~~~ 这就是本项重构的基本原则。它带来的好处是:现在我可以轻松地对compute()函数采取 Extract Method,不必担心引数(argument)传递。 ~~~ int compute () { importantValue1 = (inputVal * quantity) + _account.delta(); importantValue2 = (inputVal * yearToDate) + 100; importantThing(); int importantValue3 = importantValue2 * 7; // and so on. return importantValue3 - 2 * importantValue1; } void importantThing() { if ((yearToDate - importantValue1) > 100) importantValue2 -= 20; } ~~~