3.10Switch Statement
面向对象的一个最明显特征就是:少用switch或case语句。从本质上讲,switch语句的问题在于重复。通常用多态来解决Switch 语句带来的坏味道。switch语句常常根据type code进行选择,你要的是与该type code相关的函数或class。所以你应该使用Extract Method将switch语句提炼到一个独立的函数中,再以Move Method将他搬移到需要多态的那个class里头。此时你必须决定是否使用Replace Type Code with Subclass或Replace Type Code with State/Strategy。一旦这样完成继承结构之后,你就可以Replace Conditional with Polymorphism了。
如果你只是在单一函数中有些选择事例,而你并不想改动它们,那么“多态”就有点杀鸡用牛刀了。这种情况下Replace Parameter with Explicit Method是个不错的选择。如果你的选择条件之一是null,可以试试Introduce Null Object。
3.11Parallel Inheritance Hierarchies(平行继承关系)
在这种情况下,每当你为某个class增加一个subclass,必须也为另外一个class相应增加一个subclass。如果你发现某个继承体系的class名称前缀和另一个继承体系的class名称前缀完全相同,便是闻到了这种坏味道。消除这种重复性的一般策略是:让一个继承体系的实体(instance)指涉(参考、引用、refer to)另一个继承体系的实体。如果再接再厉运用Move Method和Move Field,就可以将指涉端的继承实体消弭。
3.12Lazy Class(冗赘类)
你所创建的每一个class,都得有人去理解它、维护它,这些工作都是要花钱的。如果一个class的所得不值其身价,它就应该消失。项目中经常会出现这样的情况:某个class原本对得起自己的身价,但重构使它身形缩水,不再做那么多工作;或开发者事前规划了某些变化,并添加一个class来对付这些变化,但变化实际上没有发生。如果某些subclass没有做满足够工作,试试Collapse Hierarchy。对于几乎没用的组件,你应该以inline class对付它们。
Collapse Hierarchy(折叠继承体系)
superclass和subclass之间无太大区别。
将它们合为一体。
动机
如果你曾经写过继承体系,你就会知道,继承体系很容易变得过分复杂。所谓重构继承体系,往往是将函数和值域在体系中上下移动。完成这些动作后,你很可能发现某个subclass并未带来该有的价值,因此需要把classes合并(折叠)起来。
作法:
选择你想移除的class:是superclass还是subclass?
使用pull up Field和pull up method或者push down method和push down field,把想要移除的class内的所有行为和数据搬移到另一个class。
每次移动后,编译并测试。
调整“即将被移除的那个class”的所有引用点,令它们改而引用合并(折叠)后留下的class。这个动作将会影响到变量的声明、参数的型别以及构造函数。
移除我们的目标;此时它应该已经成为一个空类。
编译、测试。
3.13Speculative Generality(夸夸其谈未来性)
如果所有的装置都会被用到,那就值得处理这些非必要的事情;如果用不到就不值得。用不上的装置只会挡你的路,所以,把它搬开吧。
如果函数或class的唯一用户是test cases(测试用例),这就飘出了坏味道。如果你发现这样的函数或class,请把它们连同其test case都删掉。但如果他们的用途是帮助test cases检测正当功能,当然必须刀下留人。
3.14Temporary Field(令人迷惑的暂时值域)
有时你会看到这样的对象:其内某个instance变量仅为某种特定情势而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测起当初设置的目的,会让你发疯。
请使用Extract Class给这个可怜的孤儿创造一个家,然后把所有和这个变量相关的代码都放进这个新家。
如果class中有一个复杂算法,需要好几个变量,往往就可能导致这种坏味道。由于实现者不希望传递一长串参数,所以他把这些参数都放进值域中,但是这些值域只在使用该算法的时候才有效,其他情况下只会让人迷惑。这个时候可以用Extract Class把这些变量和其相关的函数提炼到一个独立的class中,提炼后的新对象是一个method object。
3.15Message Chains(过渡耦合的消息链)
用户索求对象,然后向求得的对象继续索求对象。
这时候应该使用Hide Delegate。应该先观察Message Chain最终得到的对象是用来干什么的,看看能否以Extract Method把使用该对象的代码提炼到一个独立函数中,再运用Move Method把这个函数推入Message Chain。
Hide Delegate(隐藏委托关系)
当类图如上所示时,客户端如果想要获得对应Person的Manager,需要如下过程:
Manager manager = department.Manager;
通过重构,为类Person添加属性Manager,如:
2 {
3 private Department _department;
4 public Department Department
5 {
6 get
7 {
8 return _department;
9 }
10 set
11 {
12 _department = value;
13 }
14 }
15 public Manager Manger
16 {
17 get
18 {
19 return _department.Manager;
20 }
21 }
22 }
这样客户端可以这样写:
Manager manager = person.Manger;
当然如果person不属于任何一个部门,那么程序就会出错。这里其实可以引入Null Object来解决问题。
3.16Middle Man(中间转手人)
人们可能会过渡运用delegation。你也许会看到某个class所实现的接口有一半的函数都委托给其他class,这样就使过渡运用。这时你应该使用Remove Middle Man,直接和职责对象打交道。如果这样“不干实事”的函数只有少数几个,可以运用inline Method把它们放进调用端。如果这些Middle Man 还有其他的行为,你可以运用Replace Delegation with Inheritance把它们变成实责对象的subclass,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。
Remove Middle Man
这个重构过程和Hide Delegate刚好相反。
Replace Delegation with Inheritance
即
转变为
3.17Inappropriate Intimacy
当两个类彼此关系紧密地时候,有可能是他们所包含的方法或值域处在错误的类中而导致,此时应当通过Move Method或Move Field把它们移动到合适的位置。应当看一下两个类之间是否真的需要双向导航关系,如果不是,让它们之间的关系变为单向导航关系。或者通过Extract Class把共同的函数或值域抽取出来,让每一个Class直接去调用被抽取出来的类。另外如果是子类和父类过分耦合,则可以通过将继承转换为关联的手段来降低耦合性。
3.18Alternative Classes with Different Interfaces(异曲同工的类)
如果两个函数做同一件事,却有着不同的签名式,应当运用Rename Method根据它们的用途重新命名。请反复运用Move Method将某些行为移入classes,直到两者的协议一致为止。或许可以使用Extract Superclass来解决问题。
3.19Incomplete Library Class(不完美的程序库类)
当你面对的程序类库不够好( 或者不能满足要求)而你又不能直接改变它时,可以采用Introduce Foreign Method 方法来修改其中一两个函数,或者使用Introduce Local Extension来添加行为。
Introduce Foreign Method (162)
在client class建立一个函数,并以一个server class实体作为第一个参数。书中的例子:
Introduce Local Extension(164)
这种重构方式实际上就是从已封装的类库中继承你认为功能不足的那个类,然后添加你想要实现的功能。当然,有时候你想扩展的那个类碰巧加上了关键字final(java) 或sealed(c#),这样就没办法了。
3.20Data Class(纯稚的数据类)
所谓Data Class是指:它们拥有一些值域,以及用于访问这些值域的函数,除此之外一无长物。早期的Data Class可能会包含public值域或容器类值域,应当首先将它们封装起来(用属性或特定方法)。尽量可能的把外界操纵这些值域的方法搬移到类里面来,那些不应该被其他classes修改的值域,请运用Remove Setting Method。
Remove Setting Method
即去调设值函数或属性中的set让它变为只读。
3.21Refused Bequest
当子类不愿意继承父类的某些东西时,建议将继承修改为关联。不建议随便修改继承体系。这是一种很淡的坏味道。
3.22Comments(过多的注释)
注释本身并不是坏味道,它可以帮助我们找到代码中的坏味道。
如果你需要注释来解释一块代码做了什么,试试Extract Method;如果method已经提炼出来,但还是需要注释来解释其行为,试试Rename Mehtod;如果你需要注释说明某系系统的需求规格,试试Introduce Assertion。
当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。
如果你不知道该做什么,这才是注释的良好运用时机。除了用来记述将来的打算之外,注视还可以用来标注你并无十足把握的区域。你可以在注释里写下“为什么做某某事”。这类信息可以帮助将来的修改者,尤其是那些健忘的家伙。
Introduce Assertion
引入断言。
某一段代码需要对程序状态作出某种假设。注意,你可以创建自己的断言,而不仅仅是依赖已有断言。