函数
-
提炼函数
将大函数提炼成多个小函数,当某个代码片需要注释解释功能时,既可以被提取为一个单独的函数。
如果有局部变量,若局部变量没有被修改,可以直接当做参数传入;如果被修改,可以当做返回值。若不止一个被修改,则分解为多个函数。 -
内联函数
如果函数体的解释性比函数名更好,且复用不高,则取消函数,将函数体插入到代码片中。 -
内联临时变量
如果有个临时变量只被赋值了一次,起作用的时候妨碍了其他的重构手法,则取消临时变量,直接使用赋值表达式。 -
查询替代临时变量
临时变量只能函数内使用,将其提取为函数,则整个类都可以使用,减少参数传递。 -
引入解释性变量
较长算法中难以阅读,将长表达式分解为多个解释性临时变量,更好阅读和理解。 -
分解临时变量
临时变量应该只承担一个功能,不应该被不同的事件重复赋值使用,会导致阅读困难。尝试新建临时变量承担别的事情。 -
移除对参数的赋值
Java按值传递(对于对象其实也是引用的值传递),入参的改变不会对调用者产生影响,为了突出改变,应该新建一个临时变量进行操作。 -
以函数对象取代函数
如果函数中存在大量临时变量,可以将函数提取成一个类,临时变量作为类的成员,函数体变成类的compute()
方法,然后可以进行其他的重构方法。 -
替换算法
一定存在某个算法比另一个算法更好,使用更好的算法替换。
对象
-
搬移函数
如果一个类中的函数更高频次的被另一个类对象调用,应该将其搬移至新类中,原函数将操作委托给新类,或删除原函数。 -
搬移字段
在新类中建立名字相同的字段,并删除原字段,根据编译器报警修改引用,如果有太多引用,修改访问函数set()、get()
使其访问新类字段。 -
提炼类
类越来越复杂,如果类中某些数据和某些函数经常一起发生变化,或者发现子类化只影响部分的属性,应该考虑提取到新类中去。新类和旧类之间可能需要连接,但只有在真的需要时才去建立连接。注意,新类和旧类分离会导致权限的改变从而产生事物问题,新类的改变要依托于旧类还是独立改变要取决于具体场景。 -
类内联化
如果类起的作用很小,应该将其合并到其他类中,修改权限符并进行改名,在编译器的帮助下找到所有引用点进行更换。 -
隐藏委托关系
服务端提供委托函数来隐藏真正的委托关系,客户只需要关注委托函数,一旦真正的委托关系发生改变也只会发生在服务端,不会对客户端产生影响。 -
移除中间人
如果存在大量的委托函数是不合理的,此时应该移除委托函数,直接提供受托类对象给客户调用。 -
引入外加函数
如果你需要为提供服务的类增加一个函数,但你无法修改这个类,在客户端新建一个函数并将服务类实例作为第一个参数,该函数不应该调用客户端的任何特性,因为该函数本身就应该处于服务端中。 -
引入本地扩展
当有多个因权限无法新增的函数存在时,建立原始类的子类或包装类(子类会存在父子两个对象,注意数据不一致的情况),将外加函数引入到这个类中。
重组数据
-
自封装字段
通过访问函数访问字段,能够提供更灵活的管理方式。 -
以对象取代数值
-
值对象改变为引用对象
从一个类中衍生出很多彼此相等的示例,将它们替换为同一个对象。
值对象,即类似“jwb”、123等仅代表值的,用equals()
判断相等;类似客户与订单,若同一个客户,有多个订单时还对应着多个客户对象,即为值对象。
引用对象则代表实物,使用=
判断相等。类似客户与订单,若同一个客户,有多个订单时只对应一个客户对象,即为引用对象。 -
引用对象变为值对象
当引用对象很小且不可变,不利于管理时。 -
以对象取代数组
若一个数组其中的元素各自代表不同的东西,以对象替代数组,用字段表示每个数组元素。 -
复制被监视数据
有领域数据置身于GUI控件中,而领域函数需要访问这些数据,将该数据复制到一个领域对象中,建立一个Observe模式来同步这些数据。 -
单向关联改为双向关联
两个类都需要使用对方特性,但其间只有一条单向连接;添加一个反向指针,并使修改函数能同时更新两条连接。双向关联更加复杂,增加耦合,可能产生僵尸对象,因此谨慎使用
反向指针实现(其他技术如连接对象不在这讨论):
- 再被引用类中添加一个字段,用以保存反向指针。
- 决定由哪个类——引用端还是被引用端——控制关联关系。
- 在被控端建立一个辅助函数,其命名应该清楚指出它的有限用途。
- 如果既有的修改函数在控制端,让他负责更新反向指针。
- 如果既有的修改函数再被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。
- 一般为:一控多、主控从。
-
双向关联改为单向关联
两个类之间有双向关联,其中一个类不再需要另一个类的特性,去除不必要的关联。 -
字面常量取代魔法值
-
封装字段
将public
字段值改为private
,且提供访问函数。 可能数据隐藏且提供更灵活的管理方式。 -
封装集合
有个函数返回一个集合,让这个函数返回该集合的一个只读副本(Collections.unmodifiableXXX()
),并在这个类中提供添加/移除集合元素的函数。 -
以数据类取代记录
-
以类取代类型码
符号名只是个别名,编译器关注的是背后的真正的值,如果类型码是纯粹数据不会影响行为,使用类替代,编译器可以对类进行类型检查。 -
子类取代类型码
有一个不可变类型码,会影响类的行为,用子类取代这个类型码(类似switch、if-then-else
)。
将类型码替换为可拥有多态行为的继承体系,以类型码的宿主类为基类,并针对每一种类型码各建立一个子类。
好处在于把不同行为的了解从类用户转移到了类自身,如果需要加入新的行为变化,只需要添加一个子类,如果没有多态,就必须找到所有条件表达式并逐一修改。 -
以State/Strategy取代类型码
有一个影响类行为的类型码,但无法通过继承消除它,以状态对象取代类型码。新建一个状态码抽象类对象,并以此为基类创造类型子类。 -
以字段取代子类
各个子类的唯一差别只在返回常量数据的函数身上,修改这些函数,是它们返回超类中的某个字段,然后销毁子类。
条件表达式
-
分解条件表达式
将大块条件分支分解为多个独立函数,并重新命名。 -
合并条件表达式
若一系列条件测试都得到相同结果,则将这些测试合并成一个条件表达式,提炼为函数。 -
合并重复的条件片段
再条件表达式的每个分支上有着相同的一段代码,将其搬移到表达式之外。 -
移除控制标记
再一系列布尔表达式中,某个变量带有“控制标记”的作用,用break/return
取代控制标记 -
以卫语句取代嵌套条件表达式
一层层的嵌套逻辑难以看清正常执行路径,判断后不符合条件直接返回,取消嵌套逻辑。 -
以多态取代条件表达式
条件表达式根据对象类型有不同的行为,将条件表达式的每个分支放进一个子类内的复写函数中,将原始函数声明为抽象函数。 -
引入NULL对象
将null值替换为NULL对象,建立源类的NULL版本,设置isNull()
来辨别,在所有获取源对象却得到null的地方改为获取NULL对象,在与null比较的地方变为判断isNull()
。
建立Null接口,创建的源类NULL版本实现这个接口并继承源类,重写源类方法当为null时的情况,并使用instance of
判定是否为null对象,无需再用isNull()
简化函数调用
- 函数改名
- 添加参数
- 移除参数
- 将查询函数和修改函数分离
- 令函数携带参数
若干函数做了类似的工作,但在函数本体中却包含了不同的值,即只因几个值导致行为略有不同,此时可以将函数统一起来,将那几个值作为参数。 - 以明确函数取代参数
有一个函数完全取决于参数值而采取不同行为,针对该参数的每一个可能值,建立一个独立函数。 - 保持对象完整
当你从某个对象中取出某些值作为函数调用时的参数时,改为传递整个对象。这样以后添加参数时会更方便,且易读性增加。 - 以函数取代参数
B调用A得到的值作为参数给函数C,而C也可以调用A,此时可以去掉这个参数,直接在C中调用A即可。 - 引入参数对象
若某些参数总是很自然的同时出现,以一个对象取代这些参数。 - 移除设值函数
如果类中某个字段应该在对象创建的时候被设值,然后不再改变,应去掉该字段的所有设值函数。 - 隐藏函数
如果某个函数从来没被其他类用过,函数修改为private。 - 以工厂函数去掉构造函数
工厂函数提供更灵活的对象创建方式。 - 封装向下转型
某个函数返回的对象需要由调用者再次向下转型时,将这个向下转型下放到函数中去。 - 以异常取代错误码
- 非受控异常。由调用者负责检查
- 受控异常是函数自身的责任,应该声明可能产生的异常。
- 以测试取代异常
先提前判断条件是否通过再继续执行程序,不要滥用异常。
概括关系
- 字段上移
多个子类拥有相同的字段,将字段移至超类。 - 函数上移
有些函数在各个子类中产生完全相同的结果,将其移至超类。 - 构造函数本体上移
各子类中拥有一些构造函数,本体几乎完全一致,则在超类中新建一个构造函数,并在子类构造函数中调用它。 - 函数下移
超类中的某个函数只与部分子类有关,下放到子类中去。 - 字段下移
超类中的某些字段只有部分子类用到,下放到子类中去。 - 提炼子类
类中的某些特性纸杯某些实例用到,新建一个子类,将那部分特性移到子类中。 - 提炼超类
两个类有相似特性,建立超类并将相似特性移至超类中。 - 提炼接口
若干客户使用类接口中的同一子集,或两个类的接口有部分相同,将相同的子集提炼到一个独立接口中。 - 折叠继承体系
超类和子类无太大区别,合并。 - 塑造模板函数
有一些子类其中响应的某些函数以相同顺序执行类似操作,但各个操作细节上有所不同,将这些操作分别放入独立函数中,并保持拥有相同签名,然后将原函数上移至超类。
- 委托取代继承
某个子类只使用超类接口中的一部分,或根本不需要继承而来的数据,在子类中新建一个字段保存超类,化继承为委托。 - 继承取代委托
两个类之间有委托关系,委托类用到受托类的所有函数,且经常为整个接口编写许多简单的委托函数,则让委托类继承受托类。