8.10 Encapsulate (封装字段)
问题:你的类中存在一个public字段。
方法:将它生命为private,并且提供相应的访问函数。
动机:面向对象的首要原则就是封装,所以不应该将数据声明为public,可能会有其他对象修改这个数据,这样也会降低程序的模块化程度,比如使用private的话,可以保证数据和相关的代码比较集中,万一出现问题,也方便维护。
做法:1.为public字段提供取值/设值函数;2.找到这个类以外使用该字段的地点。如果客户只是读取该字段,就把引用替换为对取值函数的调用,如果客户更改了该字段的值,就将此引用点替换为设值得函数;3.每次修改之后编译测试;4.将字段的所有用户修改完毕后,把字段声明为private;5.编译测试。
8.11 Encapsulate Collection(封装集合)
问题:有个函数返回一个集合。
方法:让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
动机:取值函数不该返回集合自身,会让用户修改集合但是集合拥有者却一无所知,另外不应该为这个集合提供设值函数,但是应该提供为集合添加/移除元素的函数。
做法:1.加入为集合添加/移除元素的函数;2.将保存集合的字段初始化为一个空集合;3.编译;4.找出集合设值函数的所有调用者,你可以修改那个设值函数,让它使用上述新建立的添加/移除元素函数,也可以直接修改调用端,改让它们调用上述新建立的添加/移除元素函数;5.编译测试;6.找出所有“通过取值函数调用并且修改其内容”的函数。逐一修改这些函数,让他们改用添加/移除函数,每次修改后编译测试。7.修改完上述所有的“通过取值函数获取集合并修改集合内容”的函数后,修改取值函数自身,使它返回该集合的一个只读副本。8.编译测试;9.找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码。运用Extract Method(110)和Move Method(142)将这些代码移到宿主对象去;10.修改现有取值函数的名字,然后添加一个新的取值函数,使其返回一个枚举,找出旧取值函数的所有被使用点,将它们都改为使用新的取值函数;11.如果这一步跨度太大,可以先使用Rename Method(273)修改原取值函数的名称,再建立一个新取值函数用以返回枚举;最后再修改所有调用者,使其调用新的取值函数;12.编译测试。
8.12 Replace Record with Data Class(以数据类取代记录)
问题:你需要面对传统编程环境中的记录结构。
方法:为该记录创建一个“哑”数据对象。
动机:记录型结构是许多编程环境的共同性质,有一些遗留程序,可能需要通过一个传统API来与记录结构交流,或者处理从数据库读出的记录。
做法:1.新建一个类,表示这个记录;2.对于记录中的每一项数据,在新建的类中建立对应的一个private字段,并提供相应的取值/设置函数;
8.13 Replace Type Code With Class(以类取代类型码)
问题:类之中有一个数值类型码,但他并不影响类的类型。
方法:以一个新的类型替换该数值类型码。
动机:接受类型码作为参数的函数,所期望的实际是一个数值,无法强制使用符号名,会大大降低代码的可读性,从而成为bug之源。但是如果将那样的数值替换成一个类,编译器就可以进行校验,只要未这个类提供工厂函数,就可以始终保证只有合法的实例才会被创建,而且它们都会被传递给正确的宿主对象。
做法:1.为类型码建立一个类;2.修改源类实现,让它使用上述新建的类;3.编译测试;4.对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类;5.逐一修改源类用户,编译测试;6.删除使用类型码的旧接口,并删除保存旧类型码的静态变量;7.编译测试。
8.14 Replace Type Code with Subclasses(以子类取代类型码)
问题:你有一个不可变的类型码,它会影响类的行为。
方法:以子类取代这个类型码。
动机:如果面对的类型码不会影响宿主类的行为,可以使用Replace Type Code with Class(218)来处理它们,但是如果类型码会影响宿主类的行为,那么最好的办法就是借助多态来处理变化行为。一般来说就像switch这样的条件表达式,可能有switch或者if-then-else两种结构,这种情况下,应该以Replace Conditional with Polymorphism(255)进行重构,但是为了顺利进行重构,首先应该将类型码替换为可拥有多态行为的继承体系,为了建立这样的继承体系,最简单的方法就是Replace Type Code with Subclasses(223)。
做法:1.使用Self Encapsulate Field(171)将类型码自我封装起来;2.为类型码的每一个数值建立一个相应的子类,在每个子类中覆写类型码的取值函数,使其返回相应的类型码值;3.每建立一个新的子类,编译测试;4.从超类中删除保存类型码的字段,将类型码访问函数声明为抽象函数;5.编译测试。
8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码)
问题:你有一个类型码,它会影响类的行为,但是你无法通过继承手法消除它。
方法:以状态对象取代类型码。
动机:如果类型码的值在对象生命周期中发生变化或者其他原因使得宿主类不能被继承,你也可以使用本重构。
做法:1.使用Self Encapsulate Field(171)将类型码自我封装起来;2.新建一个类,根据类型码的用途为它命名,这就是一个状态对象;3.为这个新类添加子类,每个子类对应一种类型码;4.在超类中建立一个抽象的查询函数,用以返回类型码,在每个子类中覆写该函数,返回确切的类型码;5.编译;6.在源类中负责为类型码设值得函数,将查询动作转发给状态对象;7.调整源类中卫类型码设值的函数,将一个恰当的状态对象子类赋值给“保存状态对象”的那个字段;8.编译测试。
8.16 Replace Subclass with Field(以字段取代子类)
问题:你的各个子类的唯一差别只在“返回常量数据”的函数身上。
方法:修改这些函数,使它们返回超累中的某个(新增)字段,然后销毁子类。
动机:建立子类目的是为了增加新特性或改变其行为,有种变化行为被称为“常量函数”,会返回一个硬编码的值。尽管常量函数有用途,但是如果子类中只有常量函数,实在没有足够的存在价值,你可以在超类中涉及一个与常量函数返回值相应的字段,从而完全去除这样的子类,如此就可以避免因集成而带来的额外复杂性。
做法:1.对所有子类使用Replace Constructor with Factory Method(304);2.如果有任何代码直接引用子类,令它改而引用超类;3.针对每个常量函数,在超类中声明一个final字段;4.为超类声明一个protected构造函数,用以初始化这些新增字段;5.新建或修改子类构造函数,使它调用超累的新增构造函数;6.便宜额测试;7.在超类中实现所有常量函数,令它们返回相应字段值,然后将该函数从子类中删掉;8.每删除一个常量函数,编译测试;9.子类中所有的常量函数都删除后,使用Inline Method(117)将子类构造函数内联到超类的工厂函数中;10.编译测试;11.将子类删除;12.编译测试;13.重复“内联构造函数,删除子类的过程”,直到所有子类都被删除。