《重构改善既有代码的设计》之重构列表--重新组织数据(四)

来源:互联网 发布:linux重定向到文件 编辑:程序博客网 时间:2024/06/04 17:52

十二、Encapsulate Collection (封装集合)

有个函数返回一个集合。

让这个函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。

动机

我们常常会在一个类中使用集合(collection,可能是arraylistsetvector)来保存一组实例。这样的类通常也会提供针对该集合的取值、设值函数。

但是,集合的处理方式应该和其他种类的数据略有不同。取值函数不该返回集合自身,因为这会让用户得以修改集合内容而集合拥有者却一无所知。这也会对用户暴露过多对象内部数据结构信息。如果一个取值函数确实需要返回多个值,它应该避免用户直接操作对象内所保存的集合,并隐藏对象内与用户无关的数据结构。至于如何做到这一点,视你的java版本不同而有所不同。

另外,不应该为这整个集合提供一个设值函数,但应该提供用以为集合添加、移除元素的函数。这样,集合拥有者(对象)就可以控制集合元素的添加和移除。

做法

1、加入为集合添加、移除元素的函数。

2、将保存集合的字段初始化为一个空集合。

3、编译。

4、找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的添加、移除元素函数;也可以直接修改调用端,改让它们调用上述新建立的添加、移除元素函数。

有两种情况会用到集合设值函数:(1)集合为空时;(2)准备将原有集合替换为另一个集合时。

你或许会想运用Rename Method为集合设值函数改名:从setXxx()改为initializeXxx()

5、编译、测试。

6、找出所有“通过取值函数获得集合并修改其内容”的函数。逐一修改这些函数,让它们改用添加、移除函数。每次修改后,编译并测试。

7、修改完上述“通过取值函数获得集合并修改其内容”的函数后,修改取值函数自身,使它返回该集合的一个只读副本。

Java2中,你可以使用Collection.unmodifiableXxx()得到该集合的只读副本。

Java1中,你应该返回集合的一个只读副本。

8、编译、测试。

9、找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码,运用Extract Mehtod Move Method将这些代码移到宿主对象去。

如果你使用java2,那么本项重构到此为止。如果你使用java1.1,那么用户也许会喜欢使用枚举,你应该像如下这样做。

10、修改现有取值函数的名字,然后添加一个新取值函数,使其返回一个枚举。找出旧取值函数的所有被使用点,将它们都改为使用新取值函数。

11、如果这一步跨度太大,你可以先使用Rename Method 修改原取值函数的名称,再建立一个新取值函数用以返回枚举;最后再修改所有调用者,使其调用新取值函数。

12、编译、测试。

十三、Replace Type Code with Class(以类取代类型码)

类之中有一个数值类型码,但它并不影响类的行为。

以一个新的类替换该数值类型码。

动机

在以C为基础的编程语言中,类型码或枚举值很常见。如果带着一个有意义的符号名,类型码的可读性还是不错的。问题在于,符号名终究只是个别名,编译器看见的、进行检验的,还是背后那个数值。任何接受类型码作为参数的函数,所期望的实际上是一个数值,无法强制使用符号名。这会大大降低代码的可读性,从而成为bug之源。

如果把那也的数值换成一个类,编译器就可以对这个类进行类型检验。只要为这个类提供工厂函数,你就可以保证只有合法的实例才会被创建出来,而且它们都会被传递给正确的宿主对象。

但是,在使用Replace Type Code with Class之前,你应该先考虑类型码的其他替换方式。只有当类型码是纯粹数据时(也就是类型码不会在switch语句中引起行为变化时),你才能以类取代它。Java 只能以整数作为switch的判断依据,不能使用任意类,因此那种情况下不能够以类替换类型码。更重要的是:任何switch语句都应该运用 Replace Condition with Polymorphism去掉。为了进行那样的重构,你首先运用Replace Type Code with SubclassesReplace Type Code with State/Strategy, 把类型码处理掉。

即使一个类型码不会因其数值的不同而引起行为上的差异,宿主类中的某些行为还是有可能更适合置放于类型码类中,因此你还应该留意是否有必要使用Move Method将一两个函数搬过去。

做法

1、为类型码建立一个类。

这个类需要一个用以记录类型码的字段,其类型应该和类型码相同,并应该有对应的取值函数。此外还应该用一组静态变量保存允许被创建的实例,并以一个静态函数根据原本的类型码返回合适的实例。

2、修改源类实现,让它使用上述新建的类

维持原先以类型码为基础的函数接口,但改变静态字段,以新建的类产生代码。然后,修改类型码相关函数,让它们也从新建的类中获取类型码。

3、编译、测试。

4、对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类。

你需要建立“以新类实例为自变量”的函数,用以替换原先“直接以类型码为参数”的函数。你还需要建立一个“返回新类实例”的函数,用以替换原先“直接返回类型码”的函数。建立新函数前,你可以使用Rename Method修改原函数名称,明确指出哪些函数仍然使用旧式的类型码,这往往是个明智之举。

5、逐一修改源类用户,让它们使用新接口。

6、每修改一个用户,编译并测试。

你也可能需要一次性修改多个彼此相关的函数,才能保持这些函数之间的一致性,才能顺利地编译测试。

7、删除使用类型码的旧接口,并删除保存旧类型码的静态变量。

8、编译、测试。

原创粉丝点击