目录
第六章 重新组织函数
提炼函数(Extract Method)
有局部变量
- 只读局部变量的值,并不修改它
- 对局部变量再赋值
局部变量只在被提炼的代码段中使用
局部变量在被提炼的代码之外使用:让目标函数返回该变量的值
内联函数(Inline Method)
去掉某个函数直接使用其中的代码
内联临时变量(Inline Temp)
有一个临时变量只被一个简单的表达式赋值一次,将所有对该变量的引用动作,替换为对他赋值的那个表达式自身
以查询取代临时变量(Replace Temp With Query)
以一个临时变量存储某一表达式的运算结果,将这个表达式提炼到独立函数中,将这个变量的所有引用替换为对新函数的调用
引入解释性变量(Introduce Explain Variable )
将复杂表达式的结果放进一个变量,用这个变量的名称来解释该复杂表达式的用途
分解临时变量(Split Temporary Variable)
对一个临时变量的赋值超过一次,但是这个变量不是循环变量,也不用于收集结果,针对每一次赋值,创造一个新的独立的临时变量
移除对参数的赋值(Remove AssignMents Parameters)
代码对一个参数赋值,以一个临时变量取代该参数的位置
按引用传递 按值传递
以函数对象取代函数
(好复杂) 大概是把临时变量作为类的成员变量进行赋值
替换算法
将函数本体替换为另一个算法
第七章
搬移函数(Move Method)
在该函数经常引用的类中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数,或是将旧函数完全移除
搬移字段(Move Field)
程序中,某个字段被常驻类之外的另一个类更多的用到
在目标类新建一个字段,修改源字段的所有用户,使他们使用目标类的新字段
如果源字段被很多地方调用,可以先自我封装(Self Encapsulate Filed),即把为源字段封装到一个访问函数,这样在搬移字段后,就只需要修改访问函数。
提炼类(Extract Class)
某个类做了两个类应该做的事情
建立一个新类,将相关字段和函数搬移到新类
需要考虑的:要不要对用户公开这个新类
- 隐藏这个新类
在旧类中建立对新类的委托,即对新类的操作全部依赖旧类,从而完全隐藏这个新类 - 公开这个新类(可以只在一个包中公开)
公开这个新类,则无法感知是谁修改了新类的字段
解决理念:
- 将新类完全公开,旧类成为新类的引用点
- 必须通过旧类的对象修改新类对象,将新类设置为不可修改的或是提供一个不可修改的接口
将一个类分成两个类可以对两个对象分别加锁,但如果要确保两个对象同时被锁定,就面临事物问题
内联类(Inner Class)
某个类没有做太多事情,将这个类的所有特性搬移到另一个类,然后移除愿类。
隐藏委托关系(Hide Delegate)
客户通过一个委托类来调用另一个对象
在服务类上建立客户所需的所有函数,以隐藏委托关系
可以在服务类上面放置一个简单的函数,从而将委托关系隐藏起来,委托类变化时,只需服务类做出相应的修改,客户是没有感知的
移除中间人(Remove Middle Man)
某个类做了过多的委托动作,让客户直接调用委托类
当委托类的功能越来越多,每次都必须在服务类建立委托函数,服务类完全变成一个中间人,这个时候可以让客户直接调用委托类。
引入外加函数(Introduce Foreign Method)
你需要为提供服务的类新增一个函数,但你无法修改这个类。
在客户类中建立一个函数,并以第一参数的形式传入一个服务实例
这种情况适用于,抽象多次使用的函数,减少重复代码
public static void main(String[] args) {
Date now = new Date();
Date newStart = new Date(now.getYear(), now.getMonth(),now.getDate()+1);
Date newStart1 = nextDate(now);
}
public static Date nextDate(Date date){
return new Date(date.getYear(), date.getMonth(),date.getDate()+1);
}
引入本地扩展(Introduce Local Extension)
你需要为提供服务的类新增一些额外函数,但你无法修改这个类。
建立一个新类使他包含这些额外函数,让这个拓展品成为源类的子类或是包装类
这里将子类化(subclassing)和包装(wrapping)统一称为本地拓展
- 本地拓展是一个独立的类,但也是被拓展类的一个子类型;
- 它提供源类的一切特性,同时额外添加新特性;
- 在任何使用源类的地方都可以使用本地拓展取而代之;
- 子类创建的工作量少
缺点 - 子类需要在对象创建期实施,不能在对象创建后再使用本地拓展
- 子类化方案还必须产生一个子类对象,这种情况如果引用了旧对象就同时有两个对象保存源数据,会产生数据不一致问题,这种情况用包装类
- 包装类需要为源类所有的函数提供委托函数
使用包装类有一个特殊的问题:如何处理接受原始类为参数的函数
解决办法:使包装类向上兼容,即让包装类重写这类函数,使他即接收原始类又接收包装类,这样重写可以向用户隐藏包装类。
但是对于某些系统函数会出问题,比如equal
//在包装类中重写比较方法
public booolean equals(Date arg)
这样重写会违背java的交换定律,即a.equals(b)为true,那b.equals(a)也为true
这种时候只有向用户表示进行了包装
//在包装类中重写比较方法
public booolean equalsDate(Date arg)
public booolean equalsDate(DateWrap arg)
而子类化方案中不重写原始类的函数将藕不会出现这种问题
重新组织数据
自封装字段(Self Encapsulate Filed)
直接访问一个字段会使字段之间的耦合关系变得臃肿
为这个字段设置取值设值函数,并且只以这些函数来访问字段
间接访问变量的好处:
子类可以通过覆写一个函数而改变获取数据的途径
还支持更灵活的数据管理方式,比如延迟初始化
注意:
在构造函数中使用设值函数的情况,一般来说设值函数被认为应该在对象创建后再使用,所以初始化过程的行为与设值函数的行为不同。对于该字段要么设置专门的初始化函数,要么允许在构造函数中直接访问该字段。
以对象取代数据值(Replace Date Value With Object)
有一个数据项需要与其他数据和行为一起使用才有意义,将数据项变为对象
将值对象改变为引用对象(Change Value To Reference)
从一个类中衍生出许多彼此相等的实例,希望他们替换为同一个对象,将这个值对象变成引用对象
对象的分类:值对象、引用对象
值对象:完全由其所含的数据值来定义,比如钱、日期等,我们并不介意值对象的副本的存在,对值对象的比较需要覆写equal和hashcode方法
引用对象:代表真实世界中的一个事物,比如客户、账户这些东西,可以直接用==操作符来检查两个对象是否相等
在一个值对象中保存了少量不可修改的数据,然后想给这个对象加入一些可修改的数据,并且确保对任一有对象修改都能影响到引用此对象的地方,这时需要把值对象变为引用对象
将引用对象改变为值对象(Change Reference To Value)
引用对象必须被某种方式控制,我们总是需要向其控制者请求合适的引用对象---->
造成内存之间错综复杂的关系
值对象的特性:不可变,无论何时,只要调用同一对象的同一个查询函数,都应该得到同样的结果。保证了这一点,就可以放心的以多个对象表示同一事物。
如果值对象是可变的,这样就必须保证对某一对象的修改会自动更新其他“代表相同事物”的对象,这样还不如把它变成引用对象
不可变(immutable):有一个Money类表示钱的概念,包含货币和金额两种信息,那Money通常是一个不可变的值对象。
但是这不意味着薪资不可变,意味着如果要改变薪资,需要新建立一个Money对象来取代现有的 Money,而不是在现有的Money上面修改。薪资和Money的关系的可以改变,但是Money自身是不可变的。
以对象取代数组(Replace Array With Object)
数组应该只用于以某种顺序容纳一组相似的对象。
如果有一个数组,其中的元素各自代表不同的东西,以对象替换数组。对数组中的每个元素以一个字段来表示。