我们期望程序:(1)容易阅读;(2)所有逻辑都只在唯一地点指定;(3)新的改动不会危及现有行为;(4)尽可能简单表达条件逻辑。
重构是这样一个过程:它在一个目前可运行的程序上进行,在不改变程序行为的前提下使其具备上述美好的性质,使我们能够继续保持高速开发,从而增加程序的价值。
重构时机
重要原则:事不过三,三则重构。
(1)添加功能时重构;
(2)修补错误时重构;
(3)复审代码时重构。
代码坏问道
(1)重复代码;
(2)过长函数;
(3)过大的类;
(4)过长参数列;
(5)发散式变化:类定义要简单统一,功能唯一性;
(6)数据泥团:把两个类中相同的字段,许多函数签名中相同的参数,提炼到独立的类中;
重新组织函数
Extract Method(提炼函数)
有一段代码可以被组织在一起并独立出来。
提炼小函数,注意变量传递。
Replace Temp with Query(以查询取代临时变量)
你的程序以一个临时变量保存在某一表达式的运算结果。将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数可被其他函数使用。
Introduce Explaining Variable(引入解释性变量)
将复杂的表达式(或者是其中的一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。若进一步处理,临时变量又可以提炼成函数。
Split Temporary Variable(分解临时变量)
你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立,对应的临时变量。
若临时变量又多个含义,进行分解。同一个临时变量承担两件不同的事情,会令代码阅读者糊涂。
Replace Method with Method Object(以函数对象取代函数)
将这个函数放进一个单独对象中,如此一来局部变量就成了对县内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。
如何处理大型函数
在对象之间搬移特性
Move Method(搬移函数)
你的程序中,有个函数与其所驻类之外的另一个类更多交流:调用后者,或者被后者调用。
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
Extract Class(提炼类)
某个类做了应该由两个类做的事。
建立一个新类,将相关的字段和函数从旧类搬移到新类。
如果你发现子类化只影响类的部分特性,或如果你发现某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味你需要分解原来的类。
类的职责要单一。
Hide Delegate(隐藏“委托关系”)
客户通过一个委托类来调用另一个对象。在服务类上建立客户所需的所有函数,用以隐藏委托关系。
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系发生变化,客户也得相应变化。你可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。
减少调用链。
Remove Middle Man(移除中间人)
某个类做了过多的简单委托动作。让客户直接调用受托类。
委托关系达到一个平衡点。
Introduce Local Extension(引入本地扩展)
你需要为服务类提供一些额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数。让这个扩展品成吾问无为谓源类的子类或包装类。
无法修改源码的类的处理方式是子类化和包装。
一般来说,不会在扩展类中覆写原始类的函数,只会添加新函数。
重新组织数据
Replace Data Value with Object(以对象取代数据值)
你有一个数据项,需要与其他数据和行为一起使用才有意义。
对数据项的处理,有越来越多的功能要求时,就要放在一个独立的类中。
Change Value to Reference(将值对象改为引用对象)
要在引用对象和值对象之间做选择有时并不容易。有时候,你会从一个简单的值对象开始,在其中保存少量不可修改的数据。而后,你可能会希望给这个对象加入一些可修改数据,并确保对任何一个对象的修改都能影响到所有引用此一对象的地方。这时候你就需要将这个对象变成一个引用对象。
可以用来缓存数据。
使用工厂方法创建预加载数据。
Change Reference to Value(将引用对象改为值对象)
有一个引用对象,很小且不可变,而且不易管理。在分布系统和并发系统中,不可变的值对象特别有用。
Replace Array with Object(以对象取代数组)
你有一个数组,其中的元素各自代表不同的东西。以对象取代数组。对于数组中的每个元素,以一个字段表示。
字段含义更清晰。
Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
两个类都需要使用对方特性,但期间只有一条单向连接。添加一个反向指针,并使修改函数能同时更新两条连接。
决定由哪个类控制关联关系:
1. 如果两者都是引用对象,而期间的关联是“一对多”关系,那么就由“拥有单一引用”的那一方承担“控制者”角色。
2. 如果某个对象是组成另一对象的部件,那么由后者负责控制关联关系。
3. 如果两者都是引用对象,而期间关联是“多对多”关系,那么随便其中哪个对象来控制关联关系,都无所谓。
Encapsulate Collection(封装集合)
有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
对集合封装数据全部由宿主控制,包括赋值和取值。
Replace Type Code with Class(以类取代类型码)
类中有一个数值类型码,但它不影响类的行为。以一个新的类替换该数值类型码。
只有当类型码是纯粹数据时(也就是类型码不会在switch语句中引起行为变化时),你才能以类来取代它。
编译器可以对这个类进行类型检验。约束了数值范围。
Replace Type Code with Subclasses(以子类取代类型码)
你有一个不可变的类型码,它会影响类的行为。以子类取代这个类型码。
不能使用此方法的情况有:(1)类型码在对象创建之后发生了改变;(2)由于某些原因,类型码宿主类已经有了子类。如果你恰好面临这两种情况之一,就需要使用Replace Type Code with State/Strategy。
类型码处理,不同的行为有不同的处理方式。
Replace Subclas with Fields(以字段取代子类)
你的各个子类的唯一差别只在“返回常量数据”的函数身上。
减少无用子类。
简化条件表达式
Decompose Conditional(分解条件表达式)
从if,then,else三个段落中分别提炼出独立函数。
提炼小函数。
Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”。
卫语句要不就从函数中返回,要不就抛出一个异常。
使用卫语句表现所有的特殊情况。
Introduce Null Object(引入Null对象)
你需要再三检查对象是否为null。将null值替换为null对象。
特殊情况下,要分别对待空对象和特殊类。
简化函数调用
Separate Query from Modifier(将查询函数和修改函数分离)
某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。
函数要减少副作用,功能单一,返回值单一。
Replace Parameter with Explicit Methods(以明确函数取代参数)
你有一个函数,其中完全取决于参数值而采取不同行为。针对该参数的每一个可能值,建立一个独立函数。
参数值影响函数行为,把条件判断独立出函数,明确其行为。
Preserve Whole Object(保存对象完整)
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象。
有时候,你会将来自同一对象的若干项数据作为参数,传递给某个函数。这样做的问题在于:万一将来被调用函数需要新的数据项,你就必须查找并修改对此函数的所有调用。如果你把这些数据所属的整个对象传给函数,可以避免这种尴尬的处境,因为被调用函数可以向那个参数对象请求任何它想要的信息。
Replace Parameter with Methods(以函数取代参数)
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。
如果函数可以通过其他途径获得参数值,那么它就不应该通过参数取得该值。
消除参数。
Introduce Parameter Object(引入参数对象)
某些参数总是很自然地同时出现。以一个对象取代这些参数。
处理概括关系
Extract Subclass(提炼子类)
类中的某些特性只被某些(并非全部)实例用到。新建一个子类,将上面所说的一部分特性移到子类中。
子类只能用以表现一组变化。如果你希望一个类以几种不同的方式变化,就必须使用委托。
Form TemPlate Method(塑造模板函数)
你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至超类。
模板模式的应用,先提炼出小函数,然后把模板函数上移至超类,由各个子类实现对应的小函数。
Replace Inheritance with Delegation(以委托取代继承)、
某个子类只使用超类接口的一部分,或是根本不需要继承而来的数据。在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉两者之间的继承关系。
大型重构
Tease Apart Ingeritance(梳理并分解继承体系)
某个继承体系同时承担两项责任。建立两个继承体系,并通过委托关系让其中一个可以调用另一个。
要指出继承体系是否承担了两项不同的责任并不困难:如果继承体系中的某一特定层级上的所有类,其子类名称都以相同的形容词开始,那么这个体系很可能就是承担着两项不同的责任。
遵循单一继承原则,让继承体系向同一个方向发展。