重构第8章8.1-8.10

来源:互联网 发布:传奇世界怪物数据库 编辑:程序博客网 时间:2024/06/08 17:51

8.1、自封装字段

利弊分析:直接访问变量的代码比较容易阅读,间接访问变量子类可以通过复写一个函数而改变获取数据的途径还支持更灵活的数据管理方式,如:延迟初始化

什么时候用?

直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。

怎么用?

为这个字段建立取值/设值函数,并且只以这些函数来访问字段。

具体步骤:

1、  为待封装字段建立取值/设置函数

2、  找出该字段的所有引用点,将他们全部替换成取值/设置函数(可以暂时将字段改名,这样可以快速找出引用点)

3、  将字段声明为private

4、  复查,确保所有的引用点都被修改,改完后进行编译测试。

 

8.2、以对象取代数据值

什么时候用?

有一个数据项需要与其他数据项和行为一起使用才有意义。

怎么用?

将数据项变成对象

具体步骤:

1、 为待替换数值新建一个类,在其中声明一个final字段,其类型和源类中的代替换数值类型一致。然后在新类中加入这个字段的取值函数和接受此字段为参数的构造函数

2、 编译

3、 将源类中的代替换数值字段的类型改为前面新建的类

4、 修改源类中该字段的取值函数,另它调用新类的取值函数

5、 如果源类构造函数中用到这个代替换字段(多半是赋值动作),就需要修改构造函数,令它改用新类的构造函数来对字段进行赋值

6、 修改源类中待替换字段的设值函数,令它为新类创建一个实例。修改完后进行编译测试。

7、 最后,可能需要对新类使用“将值对象改为引用对象”

8.3、将值对象改为引用对象

什么时候用?

从一个类衍生出许多彼此相等的实例,希望将他们替换成同一对象。

怎么用?

将这个值对象变成引用对象。

具体步骤:

1、 使用已工厂函数取代构造函数,修改完后进行编译测试。

2、 决定由什么对象负责提供访问新对象的途径。(可能是一个静态字典或一个注册表也可以使用多个对象作为新对象的访问点)

3、 决定这些引用对象应该预先创建好还是应该动态创建(如果是预先创建好的,必须确保在去内存读取时能够被及时加载)

4、 修改工厂函数令它返回引用对象(如果引用对象时预先创建好的就需要考虑,如果有人索取一个不存在的对象该如何处理错误。可以对工厂函数改名使它传达该函数返回的是一个即存对象的意思),改完后进行编译测试。

8.4、将引用对象改为值对象

什么时候用?

有一个引用对象,很小且不可变,而且不易管理。(使用引用对象还是值对象并没有很明确的划分,如果引用对象开始变得难以使用也许就应该将它改为值对象,反之亦然。)

怎么用?

将它变成一个值对象。

具体步骤:

1、 检查重构目标是否为不可变对象,或是否可以修改为不可变对象。(若该目标可变,就使用移除设值函数,直到它为不可变为止。如果实在无法修改对象成不可变,就放弃使用这项重构)

2、 建立equals()和hashCode()(如果对象是同一个类的实例则这两个类就相等)

3、 编译,测试

4、 考虑是否可以删除工厂函数,并将构造函数声明为public

8.5、以对象取代数组

什么时候用?

有一个数组其中的元素代表不同的东西

怎么用?

以对象替换数组,对于对象中的每个元素,以一个字段来表示

具体步骤:

1、 新建一个类表示数组所拥有的信息,并在其中以一个public字段保存原先的数组

2、 修改数组的所有用户,让它们改用新类的实例,然后编译测试。

3、 逐一为数组元素添加取值/设值函数。根据元素的用途,为这些访问函数命名。修改客户端代码,让它们通过访问函数取用数组内的元素。每次修改后进行编译测试。

4、 当所有对数组的直接访问都转而调用访问函数后,将新类中保存该数组的字段声明为private。

5、 对于数组内的每一个元素,在新类中创建一个类型相当的字段。修改该元素的访问对象,令它改用上述的新建字段。

6、 每修改一个元素,编译并测试

7、 数组的所有元素都有相应的字段后,删除该数组。

8.6、复制“被监视数据”(此重构过程较复杂)

什么时候用?

有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。

怎么用?

将该数据复制到另一个领域对象中,建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。

具体步骤:

1、 修改展现类,使其成为领域类的Observer【GOF】。(如果没有领域类那就新建一个,如果没有“展现类到领域类”的关联,就将领域类保存于展现类的一个字段中)

2、 针对GUI类中的领域数据,使用自封装字段。编译测试确保程序正确

3、 在事件处理函数中调用设值函数,直接更新GUI组件。(这一步的意义是允许其中的任何动作在然后都能被执行起来)

4、 编译,测试

5、 在领域类中定义数据及其相关访问函数(确保领域类中的设值函数能够出发Observer模式的通报机制,对于被观察的数据在领域类中使用与展现类所用相同的类型来保存)

6、 修改展现类中的访问函数,将他们的操作对象改为领域对象(而非GUI组件)

7、 修改Observer的update(使其从相应的领域对象中将所需数据复制给GUI组件

8、 编译,测试

8.7、将单向关联改为双向关联

什么时候用?

两个类都需要使用对方特性。

怎么用?

添加一个反向指针,并使修改函数能够同时更新两条数据

具体步骤:

1、 在被引用类中加一个字段,用以保存反向指针

2、 决定类是引用端还是被引用端及控制关联关系

2.1、如果两者都是引用对象,而其间的关联是“一对多”关系,那么就由拥有“单一引用”的那一方承担“控制者”角色。例如一个客户有多个订单那么就由订单类来控制关联关系

2.2、如果某个对象是组成另一对象的部件,那么由后者负责控制关联关系

2.3、如果两者都是引用对象s,其间的关系时“多对多”关系,那么随便其中哪个对象控制关联关系都可以

3、 在被控端建立一个辅助函数,其命名应该清楚指出它的有限用途

4、 如果既有的修改函数在控制端,让它负责更新反向指针

5、 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并人既有的修改函数调用这个新建的控制函数。

8.8、将双向关联改为单向关联

什么时候用?

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。(双向关联虽然很有用但是维护双向连接、确保对象被正确创建和删除会增加复杂度)

怎么用?

去除不必要的关联

具体步骤:

1、 找出保存“要去除的指针”的字段,检查它的每一个用户,判断是否可以去除该指针。(不但要检查直接访问点还要检查调用这些直接访问点的函数。考虑有无可能不通过指针取得被引用对象如果有可能就可以对取值函数使用替换算法,让客户在没有指针的情况下也可以使用该取值函数。对于使用该字段的所有函数,考虑将被引用对象作为参数传进去)

2、 如果客户使用了取值函数,先运用自封装字段将待删除字段自我封装起来,然后对取值函数使用替换算法,令它不再使用该字段。然后编译测试

3、 如果客户未使用取值函数,那就直接修改待删除字段的所有引用点:改成其他途径获取该字段的所保存的对象。每次修改完后,编译并进行测试

4、 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑,然后移除该字段(如果有许多地方对此字段赋值,应先用自封装字段使这些地点改用同一个设值函数,编译并测试,然后将这个设值函数清空,在进行编译测试,如果这些都可行就可以将此字段和其设值函数的所有调用全部移除。)

5、 最后再次进行编译测试

8.9、以字面常量取代魔法数(魔法数是指有特殊意义却又不能明确表示出这种意义的数字)

什么时候用?

有一个字面数值,带有特别含义

怎么用?

创建一个常量,根据其意义为他命名,并将上述的字面数值替换为这个常量

具体步骤:

1、声明一个常量,令其值为原本的魔法数值

2、找到这个魔法数的所有引用点

3、检查是否可以使用这个新声明的常量来替换改魔法数。如果可以,便以此常量替之,然后编译测试。

8.10、封装字段(这个与自封装字段相似)

什么时候用?

类中存在一个public字段

怎么用?

将它声明为private,并提供相应的访问函数

具体步骤:

1、 为public字段提供取值/设置函数

2、 找到这个类以外使用该字段的所有地点。如果客户只是读取该字段,就把引用替换为对象取值函数的调用,如果客户修改了该字段值,就将此引用点替换为对设值函数的调用

3、 每次修改之后,编译并测试

4、 将字段的所有用户修改完毕后,将字段声明为private。

5、 编译,测试