概述
目录
- 第七章
- 第八章 重新组织数据
- 第九章
- 第十章 简化函数调用
第七章
- 在对象之间搬移特性
如果一个类有太多行为,或者与另一个类有太多合作形成高度耦合,就可以搬移函数。如果被搬移函数只引用了原类的一个字段,那么只需将这个字段作为参数传递过来,如果调用了原类的函数,那么必须将源对象传递过来。如果需要很多原类特性,那就要进一步重构,比如分解目标函数,把其中一部分移回原类。 - move field
对于一个字段,如果另一个类有更多函数使用了它,就可以考虑move。如果是public的字段,考虑先封装起来。本书写的有点早,现代java ide早就有成熟的move field refactor工具了。可以先利用ide迁移过来,再考虑是否要做封装。 - 提炼类
实际工作中,类会不断成长扩展,一开始可能只有一种功能,慢慢地随着责任不断增加,类会变得过分复杂,此时你需要考虑哪些部分可以分离出去,形成一个单独的类。某些数据和函数总是一起出现,那么它们就应该分离出去。还有一个extract class的信号:如果你发现子类化只影响类的部分特性,或者某些特性需要以另一种方式来子类化,这就意味着你需要分解原来的类。
有一个需要考虑的事情是是否公开新类,这个需要具体情况具体分析。 - inline class
如果一个类不再承担足够责任,挑选这一萎缩类的最频繁用户,以inline class手法将萎缩类塞进另一个类中。 - 隐藏委托关系 hide delegate
如果client先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系变化,client也得相应变化。可以在服务对象上放置一个委托函数,将委托关系隐藏起来。这样即使委托关系发生变化,变化也被限制在服务对象中,不会波及客户。
- 移除中间人 remove middle man
跟上一条是相反的,如果频繁使用上一条,会导致委托函数越来越多,服务类完全变成了一个中间人,此时你就应该让client直接调用受托类。
这两条规则怎么使用取决于系统需要的隐藏的程度。不过重构的好处是你不必现在就做决定,你可以在系统运行过程中不断进行调整。随着系统的变化,合适的隐藏程度这个尺度也相应改变。重构的意义:你永远不必说对不起,只要把出问题的地方修补好就行了。 - 引入外加函数
你正在使用一个第三方类,它很好用,但你又需要一个新服务,但这个类无法提供,而且你不能修改类的源码,所以你不得不在客户端编码。如果你在多处需要这段代码,就应该抽成一个函数来调用,避免重复代码。 - 引入本地扩展 Introduce Local Extension
上一条的加强版,如果引入了很多外加函数,就新建一个类来包括这些函数,可以用子类和包装类的方法来做。
第八章 重新组织数据
- 对象取代数据值
比如一开始你用一个String来表示customer,但系统后来又增加了客户地址,信用等级等信息,String明显不够用了,你需要构造一个Customer类来表示一个customer。 - 值对象改为引用对象
引用对象:每个对象对应真实世界中的一个事物,比如客户对象,账户对象。值对象:对象是否相同完全由值来定义,比如日期,钱。值对象有很多个副本存在是没有意义的浪费。有时候我们会在多个地方引用值对象,我们希望对这个对象的修改能影响到所有引用它的地方,这个时候我们需要把值对象变成引用对象。解决方案:提前创建好引用对象或动态对象,用工厂方法返回这些对象。 - 引用对象改为值对象
引用对象有时又需要转换成值对象。值对象有个非常重要的特性:不可变。这在并发系统中特别好用。比如Money对象有币种和金额两个Field,那么Money对象通常是不可变的。但并不是说你的薪资不能改变,而是在你改变薪资的时候,需要用另一个Money对象来取代现有的Money对象。解决方案:先确保是不可变对象,然后去掉工厂方法,开放构造函数,重写equals和hashcode方法 - 以对象取代数组
人们很难记住“数组的第一个元素是人名”这样的约定。如果你用对象就不一样,你可以运用字段名和函数名来传达这样的信息。 - 以字面常量取代魔法数
魔法数:拥有特殊含义,却不能明确表现出这种意义的数字。比如重力常量,π等。这些数字发生改变,就必须在程序中找到所有魔法数修改,非常麻烦。
解决方案:声明一个常量来表示魔法数。 - 封装集合
不返回集合本身,可以返回一个集合的复制给client。可以提供集合的增删方法,但不要提供set集合的方法。 - 以数据类取代记录
你可能面对一个遗留的用非面向对象语言写的程序,或者从数据库读出的记录,它们都是记录型结构(struct),这时候你有必要创建一个数据类,对于记录中的每个数据,在类中都有一个对应的field。 - 以类取代类型码
现在java中有更好的解决方案,用Enum。 - 以State/Strategy取代类型码
通过例子来看更直观些:
新建一个抽象类,把ENGINEER,MANAGER,SALESMAN变成抽象类的子类:
再把switch语句移到它应该呆的类中:
- 以字段取代子类
如果子类只是用来返回常量数据,那就可以消除它,避免继承带来的额外复杂性。
第九章
- 分解条件表达式
复杂的条件逻辑是最常导致复杂度上升的原因之一。你必须编写代码检查不同的条件分支、根据不同的分支做不同的事情,然后你就会得到一个大函数。而且条件逻辑代码会使代码更难阅读。你可以将它分解为多个独立函数,为新函数命名,更清楚地表达自己的意图。
- 合并条件表达式
- 合并重复的条件片段
- 以卫语句取代嵌套条件表达式
条件表达式有两种形式,一种是所有分支都是正常行为,第二种是只有一种是正常行为,其他都是不常见的情况。对于第一种,应该用if else的条件表达式,对于第二种,如果某个条件极其罕见,就应该单独检查该条件,并在条件为真时立刻从函数中返回。这样的单独检查被称为卫语句(guard clauses)
- 以多态取代条件表达式
- 引入Null对象
反复地判断一个对象是否为null是非常繁琐的。我们可以引入一个空对象,它不会破坏系统的逻辑,对空对象的所有请求都和真实对象一样。但这并非总是好事,有时会造成查找问题比较困难。空对象一定是常量,我们可以用单例模式来构造它。空对象需要有一个能被识别出是空对象的方法,比如isNull(),如果client需要空对象作出不同的响应,就可以调用isNull(). - 引入断言
断言不是用来检查“你认为应该为真”的条件,它是用来检查“一定必须为真”的条件的。
第十章 简化函数调用
- rename method
本书作者特别提倡将复杂的处理过程分解成小函数,但要明确小函数的用途关键就是给函数起一个好名字。函数命名有个好方法,首先考虑应该给这个函数写上一句怎样的注释,然后想办法将注释变成函数名称。 - 添加参数
使用这项重构的动机很简单:你必须修改一个函数,修改后的函数需要 一些过去没有的信息,所以你需要添加参数。但谨慎选择这项优化,因为这会增加参数列的长度,长参数往往伴随着坏味道。 - 删除参数
当一个参数不再需要的时候,删除它。 - 查询和修改分离
一个用来查询的方法,要确保它不会有修改的操作。 - 令函数携带参数
你可能会发现有两个函数:它们功能类似,只是因为少数几个值导致行为略有不同,那你可以把那几个值用参数来表示,把两个函数合并成一个。
- 以明确函数取代参数
跟上面的优化相反。如果某个参数有多种可能的值,函数内又用条件表达式检查这些值,根据不同的值做出不同的行为,那么就应该用本项重构。
- Preserve whole object(保持对象完整)
有时你会从某个对象中取出若干值,将它们作为某次函数调用时的参数。此时可以考虑把对象传给函数,这样万一将来增减函数参数时只需要在函数内部改就行了。但这会造成对象的依赖,所以需要权衡利弊。更进一步,如果一个函数用了某个对象的很多值,应该考虑把这个函数放到该对象所属的类。
- Replace Parameter with Methods(以函数取代参数)
如果函数可以通过其他途径获得参数值,那么就不应该通过参数取得该值。过长的参数列会增加理解难度。缩减参数的办法之一就是:看看接收端是否可以通过与调用端相同的计算来取得参数值,如果可以,那么就应该将这个计算过程转移到接收端内,从而去除该项参数。如果接收端有参数发送端对象的引用,那么可以直接通过引用获得参数的值,从而去掉参数列表里的参数。如果接收端没有发送端对象的引用,那就需要你自己权衡,加入一个对象的引用是否值得。 - 引入参数对象
你常会看到特定的一组参数总是一起被传递。我们可以用一个对象包装所有这些数据,再用该对象取代它们。本项重构的价值跟上面的优化类似,都是为了缩短参数列。还有一个潜在的好处是:当你把这些参数组织到一起后,往往会发现一些可被移至新建类的行为。调用函数一般会对这一组参数有一些共通的处理,如果把这些共通行为移到新对象中,可以减少很多重复代码。
将一些行为挪到新的类:
- Replace Error Code with Exception(异常取代错误码)
- replace exception with test
异常不应该被滥用。它只应该被用于异常的、罕见的行为,不应该成为条件检查的替代品。
最后
以上就是欣慰星星为你收集整理的重构-改善既有代码的设计 读书心得(二)的全部内容,希望文章能够帮你解决重构-改善既有代码的设计 读书心得(二)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复