《重构改善既有代码的设计》之重构列表--重新组织数据(一)

一、Self Encapsulate Field (自封装字段)

  你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。

为这个字段建立取值、设值函数,并且只以这些函数来访问 字段。

Private int _low,_height;

Boolean includes(int arg){

Return arg >= _low && arg <= _height;

}

转换后:

Private int _low,_height;

Boolean includes(int arg){

Return arg >= getLow() && arg <= getHeight();

}

Int getLow(return _low;)

Int getHeight(return _height;)

动机

在“字段访问方式”这个问题上,存在两种截然不同的观点:一种认为,在该变量定义所在的类中,你可以自由访问它;另一种认为,即使在这个类中你也应该只使用访问函数间接访问。两派之间的争论可以说是如火如荼。

直接访问变量的好处是:代码比较容易阅读。阅读代码的时候,你不需要停下来说:“啊,这只是个取值函数。”

如果你想访问超类的一个字段,却又想在子类中将对这个变量的访问改为一个计算后的值,这就是最该使用Self Encapsulate Field的时候。“字段自我封装”只是第一步。完成自我封装之后,你可以在子类中根据自己的需要随意覆写取值、设值函数。

做法

1、为待封装字段建立取值、设值函数。

2、找出该字段的所有引用点,将它们全部改为调用取值、设值函数。

如果引用点要读取字段值,就将它替换为调用取值函数;如果引用点要给字段赋值,就将它替换为调用字段的设值函数。

你可以暂时将该字段改名,让编译器帮助你查找引用点。

3、将该字段声明为private

4、复查,确保找出所有引用点。

5、编译、测试。

二、Replace Data Value with Object(以对象取代数据值)

你有一个数据项,需要与其他数据和行为一起使用才有意义。

将数据项变为对象。

动机

开发初期,你往往决定以简单的数据项表示简单的情况。但是,随着开发的进行,你可能会发现,这些简单数据项不再那么简单了。比如说,一开始你可能会用一个字符串来表示“电话号码”概念,但是随后你会发现,电话号码需要“格式化”、“抽取区号”之类的特殊行为。如果这样的数据项只有一两个,你还可以把相关函数放进数据项所属对象里;但是Duplicate Code坏味道和Feature Envy 坏味道很快就会从代码中散发出来。当这些坏味道开始出现,你就应该将数据值变成对象。

做法

1、为待替换数值新建一个类,在其中声明一个final字段,其类型和源类中的待替换字段数值类型一样。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数。

2、编译。

3、将源类中的待替换字段的类型改为前面新建立的类。

4、修改源类中该字段的取值函数,令它调用新类的取值函数。

5、如果源类构造函数中用到这个待替换字段(多半是赋值动作),我们就修改构造函数,令它改为新类的构造函数来对字段进行赋值动作。

6、修改源类中待替换字段的设值函数,令它为新类创建一个实例。

7、编译、测试

8、现在,你有可能需要对新类使用Change Value to Reference

三、Change Value to Reference (将之对象改为引用对象)

你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。

将这个值对象变成引用对象。

动机

在许多系统中,你都可以对对象做一个有用的分类:引用对象和值对象。前者就像“客户”、“账户”这样的东西,每个对象都代表真实世界中的一个实物,你可以直接以相等操作符(==,用来验证对象同一性)检查两个对象是否相等。后者则像是“日期”、“钱”这样的东西,它们完全由其所包含数据值来定义,你并不在意副本的存在,系统中或许存在成千上万个内容为“1/1/2012”的“日期”对象。当然,你也需要知道两个值对象是否相等,所以你需要覆写equals()(以及hashCode())。

要在引用对象和值对象之间做选择有时并不容易。有时候,你会从一个简单的值对象开始,在其中保存少量不可修改的数据。而后,你可能会希望给这个对象加入一些可修改的数据,并确保对任何一个对象的修改都能影响到所有引用此一对象的地方。这时候你就需要将这个对象变成一个引用对象。

做法

1、使用Replace Constructor with Factory Method

2、编译、测试。

3、决定由什么对象负责提供访问新对象的途径。

可能是一个静态字典或一个注册表对象。

你也可以使用多个对象作为新对象的访问点。

4、决定这些引用对象应该预先创建好,或是应该动态创建。

如果这些引用对象是预先创建好的,而你必须从内存中将它们读取出来,那么就得确保它们在被需要的时候能够被及时加载。

5、修改工厂函数,令它返回引用对象。

如果对象是预先创建好的,那么你就需要考虑:万一有人索求一个其实并不存在的对象,要如何处理错误?

你可能希望对工厂函数使用Rename Method,是其传达这样的信息:它返回的是一个既存对象。

6、编译、测试。

四、Change Reference to Value(将引用对象改为值对象)

你有一个引用对象,很小且不可变,而且不易管理。

将它变成一个值对象。

动机

如果引用对象开始变得难以使用,也许就应该将它改为值对象。引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。它们可能造成内存区域之间错综复杂的关联。在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。

值对象有一个非常重要的特性:它们应该是不可变的。无论何时,只有你调用同一对象的同一个查询函数,都应该得到同样结果。如果保证了这一点,就可以放心地以多个对象表示同一事物。如果值是可变的,你就必须确保对某一对象的修改会自动更新其他“代表相同事物”的对象。这太痛苦了,与其如此还不如把它变成引用对象。

这里有必要澄清一下“不可变”(immutable)的意思。如果你以Money类表示“钱”的概念,其中有“货币”和“金额”两条信息,那么Money对象通常是一个不可变的值对象。这并非意味着你的薪资不能改变,而是意味:如果要改变你的薪资,就需要使用另一个Money对象来取代现有的Money对象,而不是现有的Money对象上修改。你和Money对象之间的关系可以改变,但Money对象自身不能改变。

做法

1、检查重构目标是否为不可变对象,或是否可修改为不可变对象。

如果该对象目前还不是不可变的,就使用Remove Setting Method,直到它成为不可变为止。

如果将该对象修改为不可变的,就放弃使用本项重构。、

2、建立equals()hashCode();

3、编译、测试。

4、考虑是否可以删除工厂函数,并将构造函数声明为public

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值