第六章重新组织函数
本章的重构主要是针对函数进行的,假设原函数为A,原类为A,新函数和新类为B。在程序中,最麻烦的是long method,通过extract method分解函数,如果分解时,发现变量不好处理,使用 splict temporary variable处理赋值多次问题;使用replace temp query减少变量数量;如果实在搞不定,就是用 replace method with method object
Extract Method
动机:
函数太长,需要注释。
判断函数长度的标准是代码的清晰度,就算函数名称比提炼出来的代码还长也无所谓
做法:
1.处理临时变量,有三种情况:
1. A函数中出现,则将该语句放回A函数中
2. B中只读,将其作为函数B的参数
3. B中被赋值,如果不被其他地方使用,可以将该变量一起提炼到B函数中;如果变量在B调用后不使用,在B函数中直接修改即可;如果变量在B调用后还要使用,让B函数返回该变量;如果有多个返回的变量,挑选提炼的代码,保证只有一个值返回。
4. replace temp with query,减少临时变量的个数
5. replace method with method object
6. eclipse的 extract method方法
Inline Method
算是 Extract Method的逆操作
动机:
1. 内部代码和函数名同样清晰易读。
2. 有一组不太合理的函数,先组合为大函数后,重新提炼
3. 使用了太多的间接层
做法:
1. 检查函数的多态性:在各子类中查找函数名,若有实现,则不能重构
2. eclipse 的inline
Replace Temp with Query
将表达式替换为一个函数
动机:
1. 提高清晰度
2. 同一类中所有函数都可使用
做法:
1. final
2. extract method
3. 使用方法替换变量的使用,并删除变量
Split temporary variable
某个临时变量被赋值超过一次,就是临时变量有多种涵义。
做法:
1. final,编译
2. 修改到下次赋值前的变量名,使其符合涵义
3. 1和2操作
Remove Assignments to Parameters
解决问题:程序中对参数进行了设置
做法:
原始的 int fun(int val) { val=32; }
修改后: int fun(int val) { int temp=val; temp=32; }
Replace Method With Method Object
使用对象来替代函数,函数分解的终极杀器
动机:
很难拆分一个长函数
做法:
1. B,包含final字段A的对象
2. computer() 函数,原函数的参数和临时变量作为类B字段
3. A类对象和函数的参数作为构造函数的参数传入
4. B类对象b; b.computer()替换原函数的调用
第七章在对象间搬移特性
在程序设计中,决定责任放在哪是很重要,最常使用三个:通过extract class分解大类;先使用move field,再使用 move method,完成责任的迁移。
当不能访问源代码时,如果修改较少使用introduce foreign method;否则使用 introduce local extension
Extract class
某个类做了应该两个类做的事情
动机:
某些数据和函数、几项数据总是一同出现,一个测试:如果搬移了某些字段和函数,是否有其他字段或函数变得无意义
某些特性需要一种方式来子类化,某些特性需要另一种方式子类化
做法:
1. B
2. A访问B的方式
3. move method和move field完成搬移
Move field
该字段被其他类过多地用到
如果field是public,先使用 encapsulate field处理
Move method
使用前,先使用extract method处理
动机:
使用move field后
其他类用的更多
做法:
1. 检查源类的子类和超类,如果有多态性,或许无法搬移
2. 在目标类中构造新函数
3. 修改原函数为指向目标函数的委托函数
4. 修改外部的调用方式
5. 移除原函数的声明
Inline class
将这个类的所有特性搬移到另一个类中,然后删除这个类
动机:
通常是前期的重构动作移走了这个类的责任
方法:
使用eclipse菜单项
Hide Delegate
对调用端封装系统内部结构
Remove middle man
单个类做了过多的简单委托动作
和Hide Delegate是逆操作,关键是把我合适的隐藏程度
Introduce Foreign Method
动机:
无法修改源代码
要修改的函数《=2
做法:
在调用端建立函数,并以第一参数传入服务类的实例。如:
原始的: Date new=new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1)
修改后:Date new=nextDay(previous);
Private function Date nextDay(Date previous) {
Return new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1)
}
Introduce Foreign Class
动机:
无法修改源代码
要修改的函数》2
做法:
建立新类,有final的服务类的字段
第八章重新组织数据
找出数据的get方法的用户,从中找出应该存在于数据所在对象的代码,使用move field和move method处理
Self Encapsulate Field
为field建立get/set函数,通常作为其他重构方法的前戏
动机:
在子类对field的get/set进行override
Replace data value with object
将数据变为对象
动机:
有一个数据项,必须与其他数据和行为一起使用才有意义
做法:
1. class B
2. class中,创建get
3. 通过构造函数传入值
4. B类对象,通过get访问参数
Replace Array with Object
数组每个元素意义不同,如:
Row[0]=”Live” row[1]=”15”
这种情况比较少见,就不考虑了,基本思路是建立明确意义的field,使用get和set访问。
Change Bidirectional Association to Unidirectional
Change Unidirectional to Bidirectional Association
没看懂
Replace Magic Number with Constant
在程序中不要使用魔法数字,使用eclipse的 extract constants
Encapsulate Collection
集合的get/set方法。
修改为: getCourses():Unmodifiable Set; addCourse(c) removeCourse(c)
通过 Collection.unmodifiableXxx()得到集合的只读副本
Replace Type Code with Class
类中有一个类型码,但不影响类的行为
动机:
如类中的type字段
可以对这个类型进行类型检查
做法:
1. PersonType,将type和常量都移入,及getType方法;
2. Person中声明字段PersonType type,及get/set方法
3. type为 person.setPersonType(PersonType.TEACHER)
4. type为person.getPersonType().getType();
Replace Type Code with Subclass
不变的类型码,会影响类的行为
动机:
如switch或者if else结构,根据不同的类型码执行不同的动作
不适用:类型码在对象创建后发生改变;类已经有子类
本重构多和 push down method和push down field结合使用
做法:
1. type的每个常量建立一个子类
2. getType为抽象函数
3. getType返回具体的类型
Replace Type Code with Strategy/State
动机:
类型码在对象创建后发生改变
类已经有子类
当完成本重构后,使用replace conditional with polymorphism选择strategy,打算移动与状态相关的数据,使用State模式。
做法:
1. PersonType
2. getType函数
3. PersonType type字段
Replace Subclass with Fields
各自类的差别只在“返回常量数据”的函数本身,行为都一样
最终效果类似于 Replace Type Code with Class
做法:
1. 将引用子类的代码改而引用超类
2. XXXType类,包含 type和对应个子类的常量字符串
3. XXXType type
4. 删除各子类
第九章简化条件表达式
Replace Nested Conditional with Guard Clauses
条件表达式有两种情况:一种形式是所有分支都属于正常行为;一种形式是只有一个分支是正常,其他都不常见的。
本重构主要处理第二种方式,对于罕见的分支,单独检查该条件,并在条件为真时立刻返回。
Decompose Conditional
对if elseif的条件表达式分别提取独立的函数
提炼出来的函数可读性更高一些
对于嵌套条件表达式,优先使用guard clauses重构
Consolidate Conditional Expression
对相同返回结果的条件进行合并,合并的条件是从语义上是否做为同一次检查
为extract method做好准备
Consolidate Duplicate Conditional Fragments
条件表达式的每个分支上有一段相同的代码
做法:
如果共通代码位于中段,观察共同代码之前或之后的代码是否改变了什么东西,如果有改变,首先将共同代码移植表达式的起始处或尾端
如果共同代码不只一条语句,先使用extract method处理
Introduce Null Object
动机
对空对象有较多的处理,比如对null的判断,是否为空执行不同的操作等。
退而广之,可以对Special Case(某个类的特殊情况)进行处理
做法
1. Nullable { public Boolean isNull(); }
2. Customer)建立子类 NullCustomer,都是先 Nullable接口,原类返回false,子类返回true
3. Customer)建立工厂函数 static Customer newNull()
4. Customer的函数,将return null替换为return NullCustomer
5. customer==null ,替换为customer.isNull()
6. NullCustomer中作为重载函数。只有大多数调用端都要求作出相同的相应,这样才有意义
Introduce Assertion
对于函数的参数,如果一定为真(约束条件不满足,程序是否还能运行),使用assert确定。
存在重复的断言,使用extract method
第十章简化函数调用
Rename Method
做法:
使用eclipse的 Change Method Signature
当函数被子类或超类实现时:
1. 声明新函数,把旧函数代码放入新函数中
2. 旧函数调用新函数
3. 修改所有调用处,调用新函数
4. @depreciate
Add Parameter
动机:
先考虑 Introduce Parameter Object,Replace Parameter with Explict Method,Move Method
做法同上,可使用eclipse的introduce parameter
Remove Parameter
Add 的逆操作,同上
Separate Query From Modifier
动机:
如果一个函数既返回对象状态,又修改对象状态。
做法:
1. query
2. Rename method原函数为modifier,返回类型为void
3. modifier; query;
Parameter Method
行为略有不同
做法:
1. 新建带参数的函数
2. 原函数都调用该新函数
3. 如果要去掉原函数,则修改调用端
Replace Parameter with Explict Methods
参数值不同,执行的操作全不一样:针对参数的可能值建立明确的函数
是Parameter Method的逆操作。
做法:
1. 针对参数的每种可能性,建立新函数
2. 修改原函数的调用点,调用新函数
Preserve Whole Object
对象已经存在时,如果参数属于某一对象,传递该对象
做法:
1. 找出可以被对象替换的参数
2. 使用对象替换这些参数
3. 修改调用端
Introduce Parameter Object
如果对象不存在,多项参数又同时出现,则定义对象,包含这些参数和相关函数,多和move method一起使用
使用eclipse的introduce parameter object
Replace Parameter with Methods
该参数可以调用其他函数得到,则去除该参数,在函数内部调用其他函数
动机:
1. 本类函数可以计算出参数值,去掉,如果该函数依赖于调用端的某参数,则算了
2. 其他类函数计算得到,本类有哪个类的引用,去掉,如果没有引用,也不想加,则算了。
3. 参数是为了未来的灵活性,去掉
Remove Setting Method
变量只在对象构造时改变,则去除set函数,并将变量设置为final
Hide Method
该函数只在本类中使用,设为private
Replace Constructor with Factory Method
静态工厂具有名字
对于两个构造函数,如果参数类型和个数相同,则只能使用不同的顺序进行区分,而使用工厂函数可以为这两个构造函数指明不同的名称
如果有多个构造函数,可考虑用静态工厂方法替换
不要求创建新对象
当系统需要该类的N或1个实例,如果使用构造函数,则每次调用都会创建一个实例,可通过单例或多例模式重用已有实例
返回原类型的子类型对象
对于一个继承体系,在基类中声明静态工厂方法,根据type返回不同的子类实例,且该子类为package 或 private,可以向客户端隐藏子类。
命名规范
getInstance
valueOf : 返回的实例与原有实例具有相同的值,如 Float 和float , XXXConfig 和 XXX
Encapsulate Downcast
将向下转型移到函数中,也就是让函数的返回值尽可能的具体,如 return (Reading) readings.lastElements();
参数引起的错误处理
有三种方式 error code, assert exception,我倾向于使用assert处理;如果该异常不是非常罕见,可以使用error code,剩下的就给 exception 吧
第十一章处理概括关系
Pull Up Field
将子类共有的字段移到超类中。
判断若干字段是否重复,观察函数如何使用它。
在上移前将它们的名称改成相同的
使用eclipse的pull up
Pull up Method
在上移前将函数签名改成相同的。
使用eclipse的pull up
Pull Down Method
某个函数只与部分(而不是全部)子类相关:将函数移到相关子类中
使用eclipse的pull down
Extract Subclass
发现类中的某些行为只被部分实例用到,有时候这种行为是通过类型码来区分。
使用eclipse的extract class
Extract Superclass
将共有部分上移到超类,使用extract superclass
Extract Interface
1. 若干客户使用类接口中的同一子集,或两个类的接口有部分相同
2. 某个类在不同的环境下扮演不同的角色,可以针对每个角色定义一个接口
3. Runnable
方法:
Eclipse 的 extract interface
Replace Inheritance with Delegation
只使用超类的一部分,或是根本不需要继承来的数据
方法:
1. 子类中新建一个字段保存超类
2. 调整子类函数,改为通过委托调用
3. 去掉两者的继承关系
Replace Delegation with Inheritance
编写许多简单的委托函数
不能使用的:1)如果没有使用委托类的所有函数,就不应该使用继承,可以通过Remove Middle man让客户端直接调用受托函数;使用Extract SuperClass(Interface)将共有部分提炼到超类;2)受托对象被多个对象共享,而且该对象是可变的
多继承的实现:运用Extract Class先把共通行为放到一个组件时,通过委托调用该组件。
第十二章大型重构
理解不到,暂时用不上