Large Class(过大的类)
查看一个类是否“过大”,这里有一个小技巧分享给大家。就是看两点:1)这个类实例变量太多,必然会有Duplicated Code(重复代码) 2)类内如果有太多代码,也会产生Duplicated Code,让整个类看起来混乱并最终走向死亡。
因此当你察觉到这个类是一个Large Class的时候,重构的信号就来了。对于实例变量太多来说,你可以查看寻找那些彼此相关的变量,或者是他们的命名的前缀或者后缀是相同的变量,你可以通过Extract Class把他们移到别的组件中去。如果这个组件你感觉更加是否作为本类的子类,那么你可以运用Extract Subclass来进行提炼。
如果你发现有些时候,类中并非在所有时刻都使用所有实例变量,那么你可以多次使用Extract Class或者Extract Subclass。文章作者提供了一个提炼的小技巧,你如果不知道如何提炼分解这个类,你可以去查看客户端是如何使用他们的,然后通过Extract Interface提炼接口,借助于这些提炼接口,可以帮助你知道如何更好的分解这个过大类。
如果你的过大类是一个GUI类,那么你可能需要将数据和行为移动到一个独立的领域中去。并且你需要GUI和数据行为两者的数据保持同步。那么你可以使用Duplicate Observed Data来进行提炼。
Long Parameter List(过长参数列)
在对象技术出来之前,函数的参数列表往往是又臭又长,然而有时候你如果不想要这种长参数列的函数你得去依靠全局变量这种邪恶的东西。而且在我们一开始学习编程的时候老师就教导我们,函数需要什么你就在参数列表中写什么。是时候需要改变了,对象技术的出现提供了我们改变这一现状的手段。你不再需要传过长过大的参数列,因为太多参数往往会造成参数前后不一致,不易使用,更重要的是一旦你需要更多的数据,你就不得不去修改它。相反如果你通过传入对象,首先你的参数列表就很短,其次如果你想增加别的变量,会有可能只需要在函数中对这个参数对象多加一次请求就行了。
如果向已有的对象发送一条请求可以取代一个参数,那么你应该使用Replace Parameter with Method。注意是已有的参数,不是不存在的参数。这个需要理解一下,已有的参数就是函数宿主类中的某一个对象字段,也可能是函数本身存在另一个对象参数,让这个对象来替换它。如果某些数据缺乏合理的对象归属。可以使用Introduce Parameter Object来为它们制造一个“参数对象”。
有些同学可能会和我对这条有同样的疑问,有些时候我们不想增加对象与对象之间的关联。不想让被调用对象与较大对象之间有某种依赖关系。这个时候将数据从对象拆解出来单独作为参数也合情合理。但如果你此时函数的参数列表过长或者变化太频繁,你确实应该采取本手法来进行重构。
Divergent Change(发散式变化)
我们需要软件更容易被修改。遇到修改,我们希望只跳到系统的某一点,只在该处做出修改就行。但情况往往没有这么简单,因为总有那么几个类变化的原因往往是多个,他们经常会因为不同的原因在不同的方向上都要变化,都要做出适当修改。这个时候你就要注意了,比如你看到一个类说如果加入一个数据库,我需要修改其中的三个函数。如果加入一个金融工具,我需要修改其中的四个函数。面对这两个不同工具的加入,你有2个方向上的变化。那么其实你更应该用Extract Class将这个类分成两个类,每个类只针对一个变化原因进行变化,就比如类A只对数据库进行变化,类B只对金融工具发生变化。为此,你应该找出某特定原因而发生的所有变化,然后Extract到别的class中去。时刻要记住这么一句话:针对某一外界变化的所有相应修改,都应该产生在单一类中,而这个新类中的所有内容都应该反应此变化。
Shotgun Surgery(散弹式修改)
情况与Divergent Change类似,但不同点在于,Divergent Change是指一个类受多种变化的影响,而Shotgun Surgery表示针对某一变化,你都必须在不同类做出相应的修改。针对这种情况你需要使用Move Mehod和Move Field将需要修改的代码放进同一个类中,如果眼下没有合适的类的话,就创建一个。通常你也可以使用Inline Class把一系列相关行为放进同一个类,这可能会造成少量Divergent Change,但你可以轻易处理它。
Shotgun Surgery和Divergent Change你都需要适时整理重构代码,让“外界变化”和“需要修改的类”趋于一一对应。
Feature Envy(依赖情结)
面向对象技术就是将数据和行为包装在一起。一个经典的坏味道的场景就是函数都某个类的兴趣高过对自己所处类的兴趣,往往焦点就是数据。很多时候我们可以看到这种场景,类A的中的函数为了进行计算获取了类B中几乎一半的数据,面对这种情况,其实很简单,就是使用Move Method将这个函数直接移到B中去,然后让类A的调用点就调用类B的这个函数。如果一个函数中,只有一部分受这种“依恋之苦”,你应该用Extract Method把这一部分提炼出来,然后通过Move Method把这个提炼的函数移动到他所依恋的类中去。
如果出现一个函数需要用到几个类的时候,我们会很难判断究竟应该把它放哪。这个时候有个小技巧你只要记住,这个函数获取哪个类的数据最多,就把这个函数移动到哪个类中去。当然面对这种多重以来,你也可以用Extract Method将这个函数分解成一系列小函数然后移动到他们对应的需要的类中去也可以轻松完成。
文中作者也提到了设计模式GoF也有破坏这个规则的时候,我们一起来看下:Stategy和Visitor,他们通过一个间接层,将实现委托给了Stategy和Visitor,而不是直接去访问这个间接层,这样,原来函数的数据就与真正需要他们的Stategy和Visitor中的行为分开了。那么他们的目的是什么?可以说这两个模式主要是为了解决Divergent Change而设计的。总之最根本的原则就是:将总是一起变化的东西放在一块儿,数据和引用这些数据的行为总是一起变化的。但也有例外,如果例外出现,我们就搬移动那些行为,保持变化只在一个地方发生,Strategy和Visitor使你得以轻松修改函数行为,因为他们将少量需要被覆写的行为隔离开来,当然也付出了“多一层间接性”的代价。
扩展阅读