重构数据-Change Reference to Value将引用对象改为值对象四
重构数据-Change Reference to Value将引用对象改为值对象四
1.将引用对象改为值对象
1.1.使用场景
1.值对象和引用对象区别
下面通过客户Customer和订单Order两个对象介绍下它们的区别
值对象
:当一个客户Customer下了多个订单Order后,每个订单类都将创建一个客户对象,即使多个订单属于同一个客户,但每个Order对象还是拥有各自的Customer对象。所以对于多个订单Order来说没办法共享同一个客户的信息。
引用对象
:同 一客户拥有多份不同定单,代表这些定单的所有Order对象就可以共享同一个Customer对象以及对象中的所有属性信息。
2.可变对象与不可变对象
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)
值对象有一个非常重要的特性:它们应该是不可变的。如果值对象是可变的,你就必须确保对某一对象的修改会自动更新其他“代表相同事物”的对象。这太痛苦了,与其如此还不如把它变成引用对象
上面介绍值对象和引用对象区别 其中值对象就是不可变对象,引用对象是一个可变对象,他的属性会被其他线程修改。
3.使用场景
如果引用对象开始变得难以使用,也许就应该将它改为值对象。引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。
它们可能造成内存区域之间错综复杂的关联。在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
1.2.如何做
- 检查重构目标是否为不可变对象,或是否可修改为不可变对象。
- 如果该对象目前还不是不可变的,就使用Remove Setting Method (300),直到它成为不可变的为止。
- 如果无法将该对象修改为不可变的,就放弃使用本项重构。
- 建立equals()和hashCode()。
- 编译,测试。
- 考虑是否可以删除工厂函数,并将构造函数声明为public。
1.3.示例
我们从一个表示货币种类
的Currency类开始
class Currency...private String _code;public String getCode() {return _code;}private Currency (String code) {_code = code;}
这个类所做的就是保存并返回一个货币种类代码。
它是一个引用对象,所以如果要得到它的实例,必须这么做
Currency usd = Currency.get("USD");
Currency class维护一个实体链表(list of instances);我不能直接使用构造函数创建实体,因为Currency构造函数是private。
new Currency("USD").equals(new Currency("USD")) // returns false
要把一个reference object变成value object,关键动作是:检查它是否为immutable(不可变)。
如果不是,我就不能使用本项重构,因为mutable(可变的)value object会造成令人苦恼的别名现象(aliasing)。
在这里,Currency对象是不可变的,所以下一步就是为它定义equals():
public boolean equals(Object arg) {if (! (arg instanceof Currency)) return false;Currency other = (Currency) arg;return (_code.equals(other._code));}
如果我定义equals(),我必须同时定义hashCode()。实现hashCode()有个简单办法:读取equals()使用的所有值域的hash codes;然后对它们进行bitwise xor(^)操作。本例中这很容易实现,因为equals()只使用了一个值域:
public int hashCode() {return _code.hashCode();}
完成这两个函数后,我可以编译并测试。这两个函数的修改必须同时进行,否则倚赖hashing的任何群集对象(collections,例如HashTable、HashSet和HashMap)可能会产生意外行为。
现在,我想创建多少个等值的Currency对象就创建多少个。我还可以把构造函数声明为public,直接以构造函数获取Currency实体,从而去掉Currency class中的factory method和「控制实体创建」的行为。
new Currency("USD").equals(new Currency("USD")) // now returns true