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

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

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

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

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

做法:

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

例子:

class Account...
double overdraftCharge()
{
    if (m_type.isPremium())
    {
        double result = 10;

        if (m_daysOverdrawn > 7)
        {
            result += (m_daysOverdrawn - 7) * 0.85;
            return result;
        }
        else
        {
            return m_daysOverdrawn * 1.75;
        }
    }
}

double bankCharge()
{
    double result = 4.5;
    if (m_daysOverdrawn > 0)
    {
        result += overdraftCharge();
        return result;
    }
}

private:
    AccountType m_type;
    int m_daysOverdrawn;

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

class AccountType...
double overdraftCharge(int daysOverdrawn)
{
    if (isPremium())
    {
        double result = 10;

        if (daysOverdrawn > 7)
        {
            result += (daysOverdrawn - 7) * 0.85;
            return result;
        }
        else
        {
            return daysOverdrawn * 1.75;
        }
    }
}

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

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

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

class Account...
double overdraftCharge()
{
    return m_type.overdraftCharge(m_daysOverdrawn);
}

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

double bankCharge()
{
    double result = 4.5;
    if (m_daysOverdrawn > 0)
    {
        result += m_type.overdraftCharge(m_daysOverdrawn);
        return result;
    }
}

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

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

class AccountType...
double overdraftCharge(Account *account)
{
    if (isPremium())
    {
        double result = 10;

        if (account->daysOverdrawn() > 7)
        {
            result += (account->daysOverdrawn() - 7) * 0.85;
            return result;
        }
        else
        {
            return account->daysOverdrawn() * 1.75;
        }
    }
}

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

时间: 2024-12-20 01:09:33

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

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

在编程中,比较忌讳的一件事情就是长函数.因为长函数代表了你这段代码不能很好的复用以及内部可能出现很多别的地方的重复代码,而且这段长函数内部的处理逻辑你也不能很好的看清楚.因此,今天重构第一个手法就是处理长函数--Extract Method,抽取成一个独立的小函数. 我个人来说也很喜欢短小函数,因为他们代表了高强度的复用与灵活性.对于短小函数来说最最关键的就是短小函数的命名,其实你就是给了这些短小函数自我解释的机会,所以你如果给这些短小函数起一个接近其语义的名字,那当你读起长函数来说,就像是阅读

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

有时候,当你遇到一个大型函数,里面的临时变量和参数多的让你觉得根本无法进行Extract Method.重构中也大力的推荐短小函数的好处,它所带来的解释性,复用性让你收益无穷.但如果你遇到上种情况,你可能会天真的以为我只要适当的进行Replace Temp with Query,就可以把这种现象给化解.但情况往往事与愿违,不能达到你所理想的高度.这个时候你需要用到重构中的杀手锏--Replace Method with Method Object,这个手法出自Kent Beck [Beck].

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

在类与类之间搬移状态和行为,是重构过程中必不可少的步骤.很有可能在你现在觉得正常的类,等你到了下个礼拜你就会觉得不合适.或者你在下个礼拜创建了一个新的类并且你需要讲现在类的部分字段和行为移动到这个新类中.如果你发现在一个类中的某个字段,更多的被别的类的函数所使用,包括设值set和取值get函数锁取用,那么你就应该考虑搬移这个字段.当然,你也可能会去考虑是否使用Move Method去搬移这些使用这个字段的函数到这个字段的源类中去,这取决于你是否能够接受接口的变化,如果这些函数更加适合待在原地不动

『重构--改善既有代码的设计』读书笔记----代码坏味道【3】

星期六了,适当出去放松了下,回来继续我们重构的话题.今天是坏味道[3]了,很多朋友跟我私信,叫我把坏味道出完,再出手法.其实这是有道理的,很多时候,"发现"远比"怎么做"重要的多.就拿设计模式来讲,GoF里面的设计模式相信有很多人都了解过.具体的设计模式应该怎么实现啊相信有很多人都背的滚瓜烂熟,但问题的难点往往在于你应该什么时候用这个设计模式.重构也一样,手法步骤都是死的,关键在于应该发现什么时候应该重构.所以,我还是决定继续出坏味道,把坏味道全部出完我们再去学手法

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

在面向对象中,对于类这个概念我们应该有一个清晰的责任认识,就是每个类应该只有一个变化点,每个类的变化应该只受到单一的因素,即每个类应该只有一个明确的责任.当然了,说时容易做时难,很多人可能都会和我一样,一开始建立类的时候信心满满,牢记SRP原则,但随着开发进度的不断进行,很有可能你会给你原本设计好的类增加新字段或者增加新函数,对于少量的增加你可能会因为麻烦,考虑不去单独做一个新类来分解.久而久之,你这个类会变得越来越臃肿,所掌管的责任也会越来越多.这样的类往往还有大量的数据和函数,往往太大而不易

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

如果你有一个数组,其中的元素各自代表不同东西,比如你有一个 QList<QString> strList; 其中strList[0]代表选手姓名,strList[1]代表选手家庭住址,很显然这个数组表示的含义已经太多,你需要用对象来替换数组,并且对于数组中的每个元素,以一个字段来表示. 数组是一种常见的用以组织数据的数据结构,不过,它们应该只用于“以某种顺序容纳一组相似对象”.对于上面的例子你可以看到一个数组容纳了不同对象,这会给使用数组的客户带来麻烦,因为他们很难记住数组的第一个元素是姓名,

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

如果某个类没有做太多的事情,你可以将这个类的所有特性搬移到另外一个类中,然后删除原类.可以看到,Inline Class正好和Extract Class相反,后者是将一个巨类分解成多个小类从而来分担责任.这里是一个类如果不再承担足够多的责任,不再有单独存在的理由(通常是因为重构动作移除了这个类的责任),我们就会挑选这种类使用最频繁的用户(类),以Inline Class把这个类塞到这个用户类中去. 做法: 寻找源类的所有public接口,然后在你目标类上对这些public接口进行声明,并将其中的

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

同Introduce Foreign Method一样,很多时候你不能修改编辑原始类,你需要为这些服务类增加一些额外的函数,但你没有这个权限或者入口.如果你只需要一个或者两个外加函数那么你可以放心的使用Introduce Foregin Method,但是如果你发现此时有很多外加函数需要在客户类代码中中添加,你就要小心了,因为你这么做你就会让客户类变得过分复杂,责任就会过分多,你会破坏客户类的单一职责性.这个时候你就可以建立一个新类,让他来包含这些你之前所添加的额外函数,让这个扩展类成为源类的子

『重构--改善既有代码的设计』读书笔记----Replace Temp with Query

Replace Temp with Query,顾名思义,表示你用查询来替换临时变量本身,临时变量对于函数来说是只有当前函数可见的,如果你在同类的别的地方要用到这个变量你就必须重新写表达式来获取这个变量,这样的话你就会在不经意间让你的函数变得复杂起来,所以如果你想要使用Extract Method,那么Replace Temp with Query是必不可少的一个步骤.而我们前面介绍的Inline Temp其实是这个手法的一部分,两者的区别在于Inline Temp已经有了表达式自身,只需要做简