明确函数所在类的位置是很重要的。这样可以避免你的类与别的类有太多耦合。也会让你的类的内聚性变得更加牢固,让你的整个系统变得更加整洁。简单来说,如果在你的程序中,某个类的函数在使用的过程中,更多的是在和别的类进行交互,调用后者或者被后者调用,那么你就要注意了,你要去判断这个类是否真正适合他原来所在的类。

简单来说,这套手法就是在该函数最常引用的新类中建立一个有着类似行为的新函数,让旧函数变成一个单纯的委托函数或者完全删掉。

Move Method是重构理论的支柱。如果一个类的责任太多,或者一个类和别的类有太多合作,耦合太高,都需要考虑Move Method让类变得简单点。你需要多浏览你自己写的类,观察类的职责是否太多,与别的类的耦合程度问题。当然,如果你进行了Move Field,你也应该做这样的检查,因为我们都知道,函数就跟字段有关,字段都被你移动了,函数肯定要再检查一遍(如果同时需要Move Field和Move Method,一般先进行Move Filed会比较简单)。一旦发现了这样的函数,你要去分析他的调用端和被调用端,如果存在多态,你也应该去考虑继承体系中他所重定义的函数,来判断最终的移动路径。

如果你不能肯定是否该移动一个函数,你就暂且别去考虑移动这个函数,你可以转而去分析其他函数,移动其他函数往往会比你做这个决定来的简单点。如果你发现你移动了其他函数之后你仍然无法判定是否要移动这个函数,那么显然此时的这个函数移动与不移动已经不重要了。

做法:

  • 检查源类中被源函数所使用的一切特性(字段和函数),考虑它们是否应该也一起搬移。如果某个特性(函数或者字段)只被你要移动的那个函数单独用到,你可以一起搬移走。如果你发现在源类中其他函数也使用了这个特性,你应该考虑将使用这些特性的所有函数一并搬移走,有时候移动一组函数比一个一个搬移函数来的简单。
  • 检查源类的子类和超类,看看是否有该函数的其他声明。如果有其他声明,说明这个函数是呈多态性的,除非你的目标类也呈现相同的继承体系,不然你可能无法搬走。
  • 在目标类声明这个函数,接口名称可以一样也可以不一样,由你根据语境把握。
  • 将源函数的代码复制到目标函数中,调整这些代码,比如如果目标函数使用了源类中的特性(字段和函数),你就得做出决定如何让目标函数获取到源对象,如果目标类没有相应的引用机制,你应该考虑是否增加参数表,把源对象直接传过去。如果你此时使用了异常,你也应该考虑异常应该放在源类中还是放在目标类中去。
  • 编译目标类。
  • 决定如何从源函数去引用你新建的目标函数,如果存在一个现成的字段或者函数可以让你拿到目标对象那肯定最好,如果没有,考虑是否可以建立一个这样的取得目标类对象的函数,如果还是比较困难,你就该考虑是否应该给源类新建一个字段用来专门保存目标类对象,别去担心这会不会是一个永久性修改,因为后期的重构项目可能会让你把这些字段给删除掉。
  • 修改源函数,让他变成一个单纯的委托函数去调用目标函数。
  • 编译,测试。
  • 如果你经常在源类中引用目标函数,那么你保留源函数接口让他变成一个单纯的委托函数显然更容易一些。
  • 如果你要删除源函数,请将源类中对源函数的调用都替换为对目标函数的调用。你可以每修改一个点就编译测试一次,你也可以利用通过你的编辑器进行批量修改然后进行编译测试,这可能会更简单点。
  • 编译,测试。

例子:

  1. class Account...
  2. double overdraftCharge()
  3. {
  4. if (m_type.isPremium())
  5. {
  6. double result = ;
  7.  
  8. if (m_daysOverdrawn > )
  9. {
  10. result += (m_daysOverdrawn - ) * 0.85;
  11. return result;
  12. }
  13. else
  14. {
  15. return m_daysOverdrawn * 1.75;
  16. }
  17. }
  18. }
  19.  
  20. double bankCharge()
  21. {
  22. double result = 4.5;
  23. if (m_daysOverdrawn > )
  24. {
  25. result += overdraftCharge();
  26. return result;
  27. }
  28. }
  29.  
  30. private:
  31. AccountType m_type;
  32. int m_daysOverdrawn;

我们发现会根据type的不同进行相应的不同运算规则,这提示我们应该把函数放到变化的类中去,即应该放到AccoountType中去。首先,我们观察搬移函数,观察其中使用的源类特性,发现只有m_daysOverdrawn是源类的特性,但我们不选择将他搬移到目标类中而是放在源类中,因为我们可以断定,他不会随着账户的种类变化而变化,注意:这个技巧还是很重要的,将不变化的东西放到固定的类中,将容易变化的东西移动到相应的变化类中,可以让我们后期重构做更好的多态处理。对于这次重构很简单,我们可以直接以传参的方式传给目标函数。接下来,我们在AccountType中新建函数,并且命名,在这里我们可以选择同样的名称,然后把函数代码全部贴过去,然后做相应的改变

  1. class AccountType...
  2. double overdraftCharge(int daysOverdrawn)
  3. {
  4. if (isPremium())
  5. {
  6. double result = ;
  7.  
  8. if (daysOverdrawn > )
  9. {
  10. result += (daysOverdrawn - ) * 0.85;
  11. return result;
  12. }
  13. else
  14. {
  15. return daysOverdrawn * 1.75;
  16. }
  17. }
  18. }

把属于自己的isPremium()变成直接调用,以参数的形式替代源类中自己的字段。当我们需要源类的特性的时候其实有四种途径

  1. 将这个特性也搬移到目标类中
  2. 建立或使用一个从目标类到源类的引用
  3. 将源对象当作参数传给目标函数
  4. 如果所需特性只是个变量,将它作为参数传给目标函数

在这次例子中我们用了第四种方式,只是简单的传递变量参数给目标函数。调整目标函数使之通过编译之后我们进行源函数的修改,让他变成一个简单的委托,因为我们在源类中已经存在的AccountType的字段,所以改起来很简单,改完之后进行编译测试。

  1. class Account...
  2. double overdraftCharge()
  3. {
  4. return m_type.overdraftCharge(m_daysOverdrawn);
  5. }

我们可以保留源函数现在这样的样子也可以删除,如果你要删除你就要多做一步,就是找到源函数的所有调用者,然后进行替换,比如Account中的bankCharge()函数用到了这个源函数就进行相应的替换,替换完成之后你就可以删除源函数的声明了。

  1. double bankCharge()
  2. {
  3. double result = 4.5;
  4. if (m_daysOverdrawn > )
  5. {
  6. result += m_type.overdraftCharge(m_daysOverdrawn);
  7. return result;
  8. }
  9. }

如果被搬移的函数不是private,你就必须检查是否有其他类也使用了这个函数,因为C++是强类型语言,你可以简单的删除源函数的声明,让编译器来帮你进行查找。

因为在这个例子之中之用了源类的一个字段,所以我们可以简单的以传参的形式给目标函数,如果源函数中使用了源类的别的函数或者多个字段,我们就必须传源类对象给目标函数

  1. class AccountType...
  2. double overdraftCharge(Account *account)
  3. {
  4. if (isPremium())
  5. {
  6. double result = ;
  7.  
  8. if (account->daysOverdrawn() > )
  9. {
  10. result += (account->daysOverdrawn() - ) * 0.85;
  11. return result;
  12. }
  13. else
  14. {
  15. return account->daysOverdrawn() * 1.75;
  16. }
  17. }
  18. }

比如我们要获取daysOverdrawn必须要通过源类的别的函数或者需要源类的多个特性,那么此时我们就必须传源类对象过去让目标函数中进行调用。如果目标函数中需要源类的太多特性,那么你就得进一步进行重构。通常,你需要Extract Method并将其中一部分移回源类。

『重构--改善既有代码的设计』读书笔记----Move Method的更多相关文章

  1. 『重构--改善既有代码的设计』读书笔记----Extract Method

    在编程中,比较忌讳的一件事情就是长函数.因为长函数代表了你这段代码不能很好的复用以及内部可能出现很多别的地方的重复代码,而且这段长函数内部的处理逻辑你也不能很好的看清楚.因此,今天重构第一个手法就是处 ...

  2. 『重构--改善既有代码的设计』读书笔记----Replace Method with Method Object

    有时候,当你遇到一个大型函数,里面的临时变量和参数多的让你觉得根本无法进行Extract Method.重构中也大力的推荐短小函数的好处,它所带来的解释性,复用性让你收益无穷.但如果你遇到上种情况,你 ...

  3. 『重构--改善既有代码的设计』读书笔记----Move Field

    在类与类之间搬移状态和行为,是重构过程中必不可少的步骤.很有可能在你现在觉得正常的类,等你到了下个礼拜你就会觉得不合适.或者你在下个礼拜创建了一个新的类并且你需要讲现在类的部分字段和行为移动到这个新类 ...

  4. 『重构--改善既有代码的设计』读书笔记----Inline Method

    加入间接层确实是可以带来便利,但过多的间接层有时候会让我自己都觉得有点恐怖,有些时候,语句本身已经够清晰的同时就没必要再嵌一个函数来调用了,这样只会适得其反.比如 void test() { if ( ...

  5. 『重构--改善既有代码的设计』读书笔记----Replace Array with Object

    如果你有一个数组,其中的元素各自代表不同东西,比如你有一个 QList<QString> strList; 其中strList[0]代表选手姓名,strList[1]代表选手家庭住址,很显 ...

  6. 『重构--改善既有代码的设计』读书笔记----Introduce Foreign Method

    当你无法获得一个类的源代码或者没有权限去修改这个类的时候,你对于这种为你服务的类,你可能会出现需要别的需求的时候,比如一个Date类,你需要能够让他本身直接返回出他的后一天的对象,但他没有,这个时候你 ...

  7. 『重构--改善既有代码的设计』读书笔记----Extract Class

    在面向对象中,对于类这个概念我们应该有一个清晰的责任认识,就是每个类应该只有一个变化点,每个类的变化应该只受到单一的因素,即每个类应该只有一个明确的责任.当然了,说时容易做时难,很多人可能都会和我一样 ...

  8. 『重构--改善既有代码的设计』读书笔记----Introduce Local Extension

    同Introduce Foreign Method一样,很多时候你不能修改编辑原始类,你需要为这些服务类增加一些额外的函数,但你没有这个权限或者入口.如果你只需要一个或者两个外加函数那么你可以放心的使 ...

  9. 『重构--改善既有代码的设计』读书笔记----Inline Class

    如果某个类没有做太多的事情,你可以将这个类的所有特性搬移到另外一个类中,然后删除原类.可以看到,Inline Class正好和Extract Class相反,后者是将一个巨类分解成多个小类从而来分担责 ...

随机推荐

  1. [QT]构建正则表达式测试

    正则表达式是个强大的东西 暂时先记录一个用法: QString str = "Peak memory: KEY s"; QString data = "Peak memo ...

  2. 关于fixed-point

    今天又出现了shader的问题,编译到真机效果就没了,后来仔细还是因为浮点数精度的问题,后来仔细查找了些资料,才发现自己太粗心,没有看清楚 fixed-point 数据类型就乱用,这是个范围在 [-1 ...

  3. Ubuntu系统下为IDEA创建启动图标

    默认情况下,ubuntu将自动安装的软件快捷方式保存在/usr/share/applications目录下,如果我们要创建桌面快捷方式,需要在该目录下创建一个名为“idea.desktop”的文件. ...

  4. 封装获取网络信息Linux—API类

    封装获取网络信息Linux—API类 封装好的库: #ifndef NETINFORMATION_H #define NETINFORMATION_H #include <netdb.h> ...

  5. HTTP协议介绍(SERVLET)

    本文是servlet的入门篇,主要简单介绍下http协议 1.什么是HTTP _ 1.http协议:_1. 复杂解释:   http(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议 ...

  6. IOS性能调优系列:使用Instruments动态分析内存泄漏

    硬广:<IOS性能调优系列>第二篇,持续更新,欢迎关注. 第一篇介绍了Analyze对App做静态分析,可以发现应用中的内存泄漏问题,对于有些内存泄漏情况通过静态分析无法解决的,可以通过动 ...

  7. volley使用与解析(一)

    1.什么是volley Volley是google发布的基于Android平台上的网络通信库,能使网络通信更快,更简单,更健壮.获取地址:git clone https://android.googl ...

  8. Python基础知识---字典

    现在在实习期间,好久没用Python了,今天在做Java项目时用的HashMap让我联想到了Python中的字典,就写一些Python字典的知识吧,复习复习. 字典:  key --> valu ...

  9. spring项目中监听器作用-ContextLoaderListener(转)

    1 spring框架的启动入口 ContextLoaderListener 2 作用:在启动Web 容器时,自动装配Spring applicationContext.xml 的配置信息. 因为它实现 ...

  10. GWT中实现跳转及不同entrypoint怎么互相访问

    怎么跳转? 跳转这个概念这里指的是从一个web页面跳转到另一个web页面,如果我们使用gwt来开发web,很自然的我们会想到怎么从一个gwt做的页面跳转到另一个gwt做的页面. 但从网上的gwt例子来 ...