• 重构改善既有代码设计--重构手法08:Replace Method with Method Object (以函数对象取代函数)


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

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

    class Order...
    
       double price() {
    
       double primaryBasePrice;
    
           double secondaryBasePrice;
    
           double tertiaryBasePrice;
    
           // long computation;
    
           ...
    
       }

    动机:局部变量的存在会增加函数分解的难度。如果一个函数之中局部变量泛滥,那么想分解这个函数是非常困难的。Replace Temp with Query (以查询取代临时变量)可以帮助你减轻这一负担,但有时候你会发现根本无法拆解一个需要拆解的函数。这种情况下,应该使用函数对象。

           做法:1、建立一个新类,根据待处理函数的用途,为这个类命名。

                   2、在新类中建立一个const字段,用以保存原来大型函数所在的对象。我们将这个字段称为“源对象”。同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存之。

                  3、在新类中建立一个构造函数,接收源对象及原函数的所有参数。

                  4、在新类中建立一个compute()函数。

                  5、将原函数的代码复制到compute()函数中。如果需要调用源对象的任何函数,请通过源对象字段调用。

           6、编译。

           7、将旧函数的函数本体替换为这样一条语句“创建上述新类的一个新对象,而后调用其中的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;

      }

     
     总结:这种手法是当提炼函数非常困难,但确实有要提炼的时候采用的一种方法,但不是很常用,是最后的杀手锏。
     
     
  • 相关阅读:
    洛谷 P2872 [USACO07DEC]道路建设Building Roads
    cogs 29. 公路建设
    cogs 7. 通信线路
    cogs 2478. [HZOI 2016]简单的最近公共祖先
    洛谷 P1342 请柬
    洛谷 P1186 玛丽卡
    洛谷 P1491 集合位置
    启动、停止、重启服务
    洛谷——P1025 数的划分
    洛谷——P3368 【模板】树状数组 2
  • 原文地址:https://www.cnblogs.com/pony1223/p/7523656.html
Copyright © 2020-2023  润新知