《重构》第八章学习笔记
重新组织数据
本章将会介绍几个能让你更轻松处理数据的重构手法。
自封装字段(Self Encapsulate Field)
直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。
为这个字段建立取值/设值函数,并且只以这些函数来访问字段。
动机
- 如果你想访问超类的一个字段,却又想在子类中将对这个变量的访问改为一个计算后的值,这就是最该使用自封装字段方法的时候。
做法
- 为待封装字段建立设值/取值函数。
找出该字段的所有引用点,将它们全部改为调用取值/设值函数。
- 如果应用点要读取字段值,就将它替换为调用取值函数;如果引用点要给字段赋值,就将它替换为调用设值函数。
- 你可以暂时将该字段改名,让编译器帮助你查找引用点。
- 将该字段声明为private。
- 复查,确保找出所有引用点。
- 编译,测试。
以对象取代数据值(Replace Data Value with Object)
你有一个数据项,需要与其他数据和行为一起使用才有意义。
将数据项变成对象。
动机
- 开发初期,往往决定以简单的数据项表示简单的情况。但是,随着开发的进行,你可能会发现,这些数据项不再那么简单了。
做法
- 为带替换数值新建一个类,在其中申明一个final字段,其类型和源类中的待替换数值类型一样。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数。
- 编译。
- 将源类中的代替换数值字段的类型改为前面新建的类。
- 修改源类中该字段的取值函数,令它调用新类的取值函数。
- 如果源类构造函数中用到这个代替换字段(多半是赋值动作),我们就修改构造函数,令它改用新类的构造函数来对字段进行赋值动作。
- 修改源类中待替换字段的设值函数,令它为新类创建一个实例。
- 编译,测试。
- 可能需要对新类使用 Change Value to Reference (将值改为引用对象)。
将值对象改为引用对象(Change Value to Reference)
你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。
将这个值对象变成引用对象。
动机
- 有时候,你会从一个简单的值对象开始,在其中保存少量不可修改的数据,而后,你可以会希望给这个对象加入一些可修改的数据,并确保修改能够影响到所有引用此对象的地方。
做法
- 使用Replace Constructor with Factory Method(将构造函数替换为工厂方法)。
- 编译,测试。
- 决定由什么对象负责提供访问新对象的途径。
- 决定这些引用对象应该预先创建好,或是应该动态创建。
- 修改工厂函数,令它返回引用对象。
- 编译,测试。
将引用对象改为值对象(Change Reference to Value)
你有一个引用对象,很小且不可变,而且不易管理。
将它变成一个值对象。
动机
- 如果引用对象开始变得难以使用。
- 如果值对象是可变的,而你必须确保对某一对象的修改会自动更新其他“代表相同事物”的对象。
做法
- 检查重构木匾是否为不可变对象,或是否可修改为不可变对象。
- 建立equals()和hashCode().
- 编译,测试。
- 考虑是否可以删除工厂函数,并将构造函数声明为public。
以对象取代数组(Replace Array with Object)
你有一个数组,其中的元素各自代表不同的东西。
以对象替换数组。对于数组中的每个元素,以一个字段来表示。
动机
- 一个数组容纳了多种不同对象,这会给用户带来麻烦。
做法
- 新建一个类表示数组所拥有的信息,并在其中以一个public字段保存原先的数组。
- 修改数组的所有用户,让它们改用新类的实例。
- 编译,测试。
- 逐一为数组元素添加取值/设值函数。根据元素的用途,为这些访问函数命名。修改客户端代码,让它们通过访问函数取用数组内元素。每次修改后编译并测试。
- 当所有对数组的直接访问都转而调用访问函数后,将新类中保存该数组的字段声明为private。
- 编译。
- 对于数组内每个元素,在新类中创建一个类型相当的字段,修改该元素的访问函数,令它改用上述的新建字段。
- 每修改一个元素,编译并测试。
- 数组的所有元素都有了相应字段之后,删除该数组。
复制“被监视数据”(Duplicate Observed Data)
你有一些领域数据置身与GUI控件中,而领域函数需要访问这些数据。
将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内重复数据。
动机
- 如果遇到的代码是两层方式开发,业务逻辑被内嵌于用户见面之中,就有必要将行为分离出来。其中主要工作就是函数的分解和搬移。但数据就不同了,不能仅仅移动数据,必须将它复制到新对象中,并提供相应的同步机制。
做法
修改展现类,使其成为领域类的Observer。
- 如果尚未由领域类,就新建一个。
- 如果没有“从展现类到领域类”的关联,就将领域类保存于展现类的一个字段中。
- 针对GUI类中的领域数据,使用Self Encapsulate Field(子封装字段)。
- 编译,测试。
- 在事件处理函数中调用设值函数,直接更新GUI组件。
- 编译,测试。
在领域类中定义数据及其相关访问函数。
- 确保领域类中的设值函数能够出发Observer模式的通报机制。
- 对于被观察的数据,在领域类中使用与展现类所用的相同类型来保存。后续重构可以自由改变这个数据类型。
- 修改展现类中的访问函数,将它们的操作对象改为领域对象(而非GUI组件)。
- 修改Observer的更新方法,使其从相应领域对象中将所需数据复制给GUI组件。
- 编译,测试。
将单向关联改为双向关联(Change Unidirectional Association to Bidirectional)
两个类都需要使用对方特性,但其间只有一条但相连接。
添加一个反向指针,并使修改函数能够同时更新两条连接。
动机
- 随着时间的推移,你可能发现被引用类需要得到引用类的某些数据或行为。
做法
- 在被引用类中增加一个字段,用以保存反向指针。
- 决定由哪个类一一引用端还是被引用端一一控制关联关系。
- 在被控端建立一个辅助函数,其命名应该清楚指出它的有限用途。
- 如果既有的修改函数在控制端,让它负责更新反向指针。
- 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。