1. 简化函数调用
1.1 Rename Method(函数改名)
函数的名称未能揭示函数的用途,修改函数名。
动机
函数的名称不能反映它的用途。
做法
- 检查函数签名是否被超类或子类实现过。如果是,则需要针对每份实现分别进行下列步骤。
- 声明一个新函数,将它命名为你想要的新名称。将旧函数的代码复制到新函数中,并进行适当调整。
- 编译。
- 修改旧函数,令它将调用转发给新函数。
- 编译,测试。
- 找出旧函数的所有被引用点,修改它们,令它们改而引用新函数。每次修改后,编译并测试。
- 删除旧函数。
- 编译,测试。
范例:略。
1.2 Add Parameter(添加参数)
某个函数需要从调用端得到更多信息。为此函数添加一个对象参数,让该对象带进函数所需信息。
动机
你需要添加参数才能完成工作。
做法
- 检查函数签名是否被超类或子类实现过。如果是,则需要针对每份实现分别进行下列步骤。
- 声明一个新函数,名称与原函数同,只是加上新添参数。将旧函数的代码复制到新函数中。
- 编译。
- 修改旧函数,令它调用新函数。
- 编译,测试。
- 找出旧函数的所有被引用点,将它们全部修改为对新函数的引用。每次修改后,编译并测试。
- 删除旧函数。
- 编译,测试。
范例:略。
1.3 Remove Parameter(移除参数)
函数本体不再需要某个参数,将该参数去除。
动机
函数不再需要某个参数。
做法
- 检查函数签名是否被超类或子类实现过。如果是,则需要针对每份实现分别进行下列步骤。
- 声明一个新函数,名称与原函数同,只是去除不必要的参数。将旧函数的代码复制到新函数中。
- 编译。
- 修改旧函数,令它调用新函数。
- 编译,测试。
- 找出旧函数的所有被引用点,将它们全部修改为对新函数的引用。每次修改后,编译并测试。
- 删除旧函数。
- 编译,测试。
范例:略。
1.4 Separate Query from Modifier(将查询函数和修改函数分离)
某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。
动机
某个函数既返回对象状态值,又修改对象状态。
做法
- 新建一个查询函数,令它返回的值与原函数相同。
- 修改原函数,令它调用查询函数,并返回获得的结果。
- 编译。
- 编译,测试。
- 将调用原函数的代码改为调用查询函数。然后,在调用查询函数的那一行之前,加上对原函数的调用。每次修改后,编译并测试。
- 将原函数的返回值改为void,并删掉其中的所有return语句。
范例:略。
1.5 Parameterize Method(令函数携带参数)
若干函数做了类似的工作,但在函数本体中却包含了不同的值。建立单一函数,以参数表达那些不同的值。
动机
若干函数大部分代码重复。
做法
- 新建一个带有参数的函数,使它可以替换先前所有的重复性函数。
- 编译。
- 将调用旧函数的代码改为调用新函数。
- 编译,测试。
- 多所有旧函数重复上述步骤,每次替换后,修改并测试。
范例:略。
1.6 Replace Parameter with Explicit Methods(以明确函数取代参数)
你有个函数,其中完全取决于参数值而采取不同行为。针对该参数的每一个可能值,建立一个独立函数。
动机
如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数值作出不同的行为,这时你就应该使用本项重构。
做法
- 针对参数的每一种可能值,新建一个明确函数。
- 修改条件表达式的每一个分支,使其调用合适的新函数。
- 修改每个分支后,编译并测试。
- 修改原函数的每一个被调用点,该而调用上述的某个合适的新函数。
- 编译,测试。
- 所有调用端都修改完毕后,删除原函数。
范例:略。
1.7 Preserve Whole Object(保持对象完整)
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象
动机
函数的若干项参数属于同一个对象。
做法
- 对你的目标函数新添加一个参数项,用以代表原数据所在的完整对象。
- 编译,测试。
- 判断哪些参数可被包含在新添的完整对象中。
- 选择上述参数之一,将被调用函数中原来引用该参数的地方,改为调用新添参数对象的相应取值函数。
- 删除该项参数。
- 编译,测试。
- 针对所有可从完整对象中获得的参数,重复上述过程。
- 删除调用端中那些带有被删除参数的代码。
- 编译,测试。
范例:略。
1.8 Replace Parameter with Methods(以函数取代参数)
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。
动机
如果函数可以通过其他途径获得参数值,那么它就不应该通过参数取得该值。
做法
- 如果有必要,将参数的计算过程提炼到一个独立的函数中。
- 将函数本体内引用该参数的地方改为调用新建的函数。
- 每次替换后,修改并测试。
- 全部替换完成后,使用Remove Parameter将该参数去掉。
范例:略。
1.9 Introduce Parameter Object(引入参数对象)
某些参数总是很自然地同时出现。以一个对象取代这些参数。
动机
你常会看到特定的一组参数总是一起被传递。这样的一组参数就是所谓的Data Clumps(数据泥团),我们可以运用一个对象包装所有这些数据,再以该对象取代它们。
做法
- 新建一个类,用以表现你想替换的一组参数。将这个类设为不可变的。
- 编译。
- 针对使用该组参数的所有函数,实施Add Parameter,传入上述新建类的实例对象,并将此参数设为null。
- 对于Data Clumps中的每一项,从函数签名中移除之,并修改调用端和函数本体,令它们都改而通过新的参数对象取得该值。
- 每去除一个参数,编译并测试。
- 将原先的参数全部去除之后,观察有无适当函数可以运用Move Method搬移到参数对象之中。
范例:略。
1.10 Remove Setting Method(移除设值函数)
类中的某个字段应该在对象创建时被设值,然后就不再改变。去掉该字段的所有设值函数。
动机
如果你不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数(同时将该字段设为final)。
做法
- 检查设值函数被使用情况,看它是否只被构造函数调用,或者被构造函数所调用的另一个函数调用。
- 修改构造函数,使其直接访问设值函数所针对的那个变量。
- 编译,测试。
- 移除这个设值函数,将它所针对的字段设为final。
- 编译,测试。
范例:略。
1.11 Hide Method(隐藏函数)
有一个函数,从来没有被其他任何类用到。将这个函数修改为private。
动机
检查函数是否有必要对外公开。
做法
- 经常检查有没有可能降低某个函数的可见度。
- 尽可能降低所有函数的可见度。
- 每完成一组函数的隐藏之后,编译并测试。
范例:略。
1.12 Replace Constructor with Factory Method(以工厂函数取代构造函数)
你希望在创建对象时不仅仅是做简单的建构动作。将构造函数替换为工厂函数。
动机
检查函数是否有必要对外公开。
做法
- 新建一个工厂函数,让它调用现有的构造函数。
- 将调用构造函数的代码改为调用工厂函数。
- 每次替换后,编译并测试。
- 将构造函数声明为private。
- 编译。
范例:略。
1.13 Encapsulate Downcast(封装向下转型)
某个函数返回的对象,需要由函数调用者执行向下转型。将向下转型动作移到函数中。
动机
函数返回的对象需要向下转型。
做法
- 找出必须对函数调用结果进行向下转型的地方。
- 将向下转型动作搬移到该函数中。
范例:略。
1.14 Replace Error Code with Exception(以异常取代错误码)
某个函数返回一个特定的代码,用以表示某种错误情况。改用异常。
动机
程序中某些函数返回一个错误码来代表程序运行有异常。
做法
- 决定应该抛出受控异常还是非受控异常。
- 找到该函数的所有调用者,对它们进行相应调整,让它们使用异常。
- 修改该函数的签名,令它反映出新用法。
范例:略。
1.15 Replace Exception with Test(以测试取代异常)
面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查。
动机
在该用条件检查的地方使用了异常。异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为。
做法
- 在函数调用点之前,放置一个测试语句,将函数内catch区段中的代码复制到测试句的适当if分支中。
- 在catch区段起始处加入一个断言,确保catch区段绝对不会被执行。
- 编译,测试。
- 移除所有catch区段,然后将try区段内的代码复制到try之外,然后移除try区段。
- 编译,测试。
范例:略。
本文引用自图灵程序设计丛书:重构 改善改善既有的代码设计。