1 Self Encapsulate Field(自封装字段)
直接访问一个字段,会导致字段之间的的耦合关系过于笨拙。为字段建立取值/设值函数,并且只以这些函数来访问字段。
动机:可以通过覆写函数而改变获取数据的途径。
做法:
- 为带封装字段建立取值/设值函数。
- 找出该字段的所有引用点,替换。
- 声明该字段为private。
- 复查,确保覆盖所欲引用点。
- 编译,测试。
2 Replace Data Value with Object(以对象取代数据值)
数据项需要与其他数据和行为一起使用才有意义。
动机:当数据开始变得复杂,操作数据的操作开始变多,就会变成这样。
做法:
- 为替代数值新建一个类,在其中声明一个final字段,其类型和源类中的替换数据类型一样。然后在新类中加入这个字段的取值函数,再加上一个此字段的构造函数。
- 编译。
- 将源类中的待替换数值字段类型改为前面新建的类。
- 修改源类中改字段的取值函数,令它调用新类的取值函数。
- 如果源类的构造函数中用到这个带替换字段,我们修改构造函数,令他改用新函数的构造函数,令它改用新类的构造函数来对字段进行赋值动作。
- 吸怪源类中待替换字段的设值函数,令他为新类创建一个实例。
- 编译,测试。
- 现在,可能要对新类使用Change Value to Reference。
3 Change Value to Reference(将值对象改为引用对象)
从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象变成引用对象。
Motivation:在引用对象和值对象间做选择有时不容易。有时候,你会从一个简单的值对象,在其中保存少量不可修改的数据。而后,你可能会希望给这个对象加入一些可修改数据,并确定对任何一个对象的修改都能影响到所有引用此一对象的地方。这时候需要将对象变成一个引用对象。
做法:
- 使用Replace Constructor with Factory Method。
- 编译测试。
- 决定由什么对象负责提供访问新对象的途径。
- 决定这些引用对象应该预先创建好还是动态创建。
- 修改工厂函数,令他返回引用对象。
- 编译,测试。
4 Change Reference to Value
有一个引用对象,很小且不变,而且不易管理。将它变成一个值对象。
Motivation:如果引用对象开始变得难以使用,也许就应该将它改为值对象。引用对象将会早成很复杂的关联。特别是在分布式系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
做法:
- 检查重构目标是否位不可变对象,或是否可修改位不可变对象。
- 建立equals()和hashCode()。
- 编译,测试。
- 考虑是否可以删除工厂函数,并将构造器声明位public。
5 Replace Array with Object(以对象取代数组)
数组的各个元素各自代表的意义不同。以对象替换数组,对于数组中的每个元素,以一个字段来表示。
Motivation:数组应该是用来组织容纳一组相似的对象。如果数组里容纳的对象的意义不同,那么会给带来很臭的味道。
- 新建一个类表示数组所拥有的信息,并在其中以一个public字段保存原先存储的数组。
- 修改数组的所有用户,让它们改用新类的实例。
- 编译,测试。
- 逐一位数组元素添加取值/设值函数。根据元素的用途,为这些访问函数命名。修改客户端代码,让它们通过访问函数取用数组内的元素。每次修改后编译并测试。
- 当所有对数据的直接访问都转而调用访问函数后,将新类中保存该数组的字段申明位private。
- 编译。
- 对于数组内的每一个元素,在新类中创建一个类型相当的字段。修改该元素的访问函数,另它改用上述的新建字段。
- 每修改一个元素,编译并测试。
- 数组的所有元素都有了相应的字段之后,删除该数组。
6 Duplicate Observed Data(复制“被监视的数据”)
将该数据赋值到一个领域对象中。建立一个Observer模式,以同步对象和GUI对象内的重复数据。其实就是用Observer模式改造类。
Motivation:一个分层良好的系统,应该将处理用户界面和处理业务逻辑分开。
7 Change Unidirectional Association to Bidirection
添加一个反向指针,并使修改函数能够同时更新两条链接。
Motivation:被引用类需要得到其引用者以便进行某些处理。这时候就加入了反向指针。
做法:
- 在被引用类中增加一个字段,用以保存反向指针。
- 决定由哪个类——引用端还是被引用端——控制关联关系。
- 在被控制端建立一个辅助函数,其命名应该清楚指出它的有限用途。
- 如果既有的修改函数在控制端,让它负责更新反向指针。
- 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。
tips:下面是如何决定由哪一个类负责控制关联关系。
1.如果两者都是引用对象,关联是一对多的关系。那就由拥有单一引用的那一方承担控制者角色。
2.如果某个对象是组成另一个对象的部件,那么后者控制关联关系。
3.如果两者都是引用对象,关联是多对多,那么随便选一个,是哪个都无所谓。
8 Change Bidirectional Association to Unidirectional
去掉不必要的关联。
Motivation:大量双向连接很容易造成“僵尸对象”;此外双向关联也迫使连个类之间有了依赖。
做法:
- 找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除指针。
- 如果客户使用了取值函数,先运用Self Encapsulate Field 将待删除字段自我封装起来,然后使用Substitute Algorithm对付取值函数,令它不再使用该字段。
- 如果客户并未使用取值函数,那就直接修改待删除字段的所有被引用点:改以其他途径获得字段所保存的对象。每次修改后,编译并测试。
- 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑。然后移除该字段。
9 Replace Magic Number with Symbolic Constant
创建一个常亮,根据其意义为它命名,并将上述的字面数值替换位这个常亮。
10 Encapsulate Field
将类中存在的public字段声明位private,并提供相应的访问函数。
11 Encapsulate Collection
让返回集合的函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
Motivation:如果直接返回集合的话,集合发生变化,我们的类是一无所知的,这是很可怕的事情;且暴露集合给用户也就是将类的过多实现细节暴露给用户。如果一个取值函数确实需要返回多个值,它应该避免用户直接操作对象内保存的集合,并隐藏对象内与用户无关的数据结构。另外,不应该位集合提设值函数,应该提供用以为集合添加/移除元素的函数。
做法:
- 加入为集合添加/移除元素的函数。
- 将保存集合的字段初始化位一个空集合。
- 编译。
- 找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的添加/删除函数;也可以直接修改调用段。
- 编译,测试。
- 找出所有”通过取值函数获得集合并修改内容“的函数,修改这些函数,让它们改用添加/移除函数。每次修改后,编译并测试。
- 修改玩上述所有“通过取值函数获得集合并修改集合内容”的函数后,修改取值函数自身,使它返回该集合的一个只读副本(或者是迭代器)。
- 编译测试。
- 找出取值函数的所有用户,从中找出应该存在与集合所属对象内的代码。Extract Method 和 Move Method到宿主对象去。
12 Replace Record with Data Class(以数据类取代记录)
你需要面对传统变成环境中的记录结构,为该记录创建一个‘哑’数据对象。
Motivation:总有可能遇到记录格式,那么就把记录转换位类把。
做法:
- 新建一个类,表示这个记录。
- 对于记录中的每一项数据,在新建的类中建立对应一个private字段,并提供相应的getter/setter。
13 Replace Type Code with Class(以类取代类型码)
类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码。
Motivation: 在以C位基础的变成语言中,类型码和枚举值还是很常见的,类型码可读性还是不错。但问题在于,类型码终归是别名,编译器看见的,进行类型检测的还是还是数值。使用类型码的实质其实就是使用一个数值,无法强制使用符号名。这会大大降低代码的可读性,滋生bug。
做法:
- 位类型码建立一个类
- 修改源类实现,让它使用上述新建的类。
- 编译,测试。
- 对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新的类。
- 注意修改源用户,让它们使用新接口。
- 每修改一个用户,编译并测试。
- 修改使用类型码的旧借口,并删除保存旧类型码的静态变量。
- 编译,测试。
14 Replace Type Code with Subclasses(以子类代替类型码)
有一个不可变的类型码,会影响类的行为。以子类取代这个类型码。
Motivation:如果面对的是不会影响宿主类的行为,可以使用Replace Type Code with Class。如果类型码会影响宿主类的行为,那么最好的办法就是借助多态去解决它。一般来说这种情况的标志就是像switch这种表达式。这种条件表达式可能有两种表现形式:switch或者是if-then-else结构。这种情况必须重构,用的是Replace Conditional with Polymorphism进行重构。但为了能够顺利进行那样的重构,首先应该将类型码转换为可拥有多态行为的继承体系。这样的一个继承体系应该以类型码宿主为基类,针对每种类型码各建立一个子类。
做法:
- 使用Self Encapsulate Field将类型码自我封装起来。
- 为类型码的每一个数值建立一个相应的子类。每个子类中覆写类型码的取值函数,使其返回相应的类型码值。
- 每建立一个新的子类,编译并测试。
- 从超类中删掉保存类型码的字段。将类型码访问函数声明为抽象函数。
- 编译,测试。
15 Replace Type Code with State/Strategy
有一个类型码,会影响类的行为,但无法继承手法消除。以状态对象取代类型码。
Motivation:和Replace Type Code with Subclasses很相似,但是类型码在生命期间发生变化或者其他原因宿主不能被继承。可以用此方法重构。主要使用State模式或策略模式。
- 使用Self Encapsulate Field将类型码自我封装起来。
- 新建一个类,根据类型码的用途为他命名。
- 位这个新类添加子类,每个子类对应一种类型码。
- 在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码。
- 编译。
- 在源类中建立一个字段,用以保存新建的状态对象。
- 调整源类中负责查询类型码的函数,将查询动作转发给状态对象。
- 调整源类中为类型码设值的函数,将一个恰当的状态对象子类赋值给“保存状态对象字段”。
- 编译,测试。
16 Replace Subclass with Field
你的各个子类的唯一差别只在“返回常量数据”的函数身上。修改这些函数,使它们返回 超类中的某个字段,然后销毁子类。
Motivation:有一种变化行为叫做“常量函数”,它们会返回一个硬编码的值。若子类只有常量函数,是在没有足够存在的价值,应该去掉以避免额外的复杂性。
做法:
- 对所有子类使用Replace Constructor with Factory Method。
- 如果有任何代码直接引用子类,令它改而引用超类。
- 针对每个常量函数,在超类中声明一个final。
- 为超类声明一个protected构造函数,用以初始化新增字段。
- 新建或修改子类构造函数,使它调用超类的新增构造函数。
- 编译,测试。
- 在超类实现素有常量函数,令它们返回相应字段值,然后将该函数从子类中删除。
- 每删除一个常量函数,编译并测试。
- 子类中所有常量函数都被删除后,使用Inline Method将子类构造函数内联到超类的工厂函数中。
- 编译,测试。
- 将子类删除掉。
- 编译,测试。
- 重复内联构造函数,删除子类,知道所有子类删除完。