Summary:
你有一个引用对象,很小且不可变,而且不易管理。将它变成一个值对象。
Motivation:
如果引用对象开始变得难以使用,也许就应该将它改为值对象。引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。它们可能造成内存区域之间错综复杂的关联。在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
值对象有一个非常重要的特性:它们应该是不可变的。无论何时,只要你调用同一个对象的同一个查询函数,都应该得到同样的结果。如果保证了这一点,就可以放心地以多个对象表示同一个事物。如果值对象是可变的,就必须确保对某一对象的修改会自动更新其他“代表相同事物”的对象。这台痛苦了,与其如此还不如把它变成引用对象。
这里有必要澄清一下“不可变”的意思。如果以Money类表示“钱”的概念,其中有“币种”和“金额”两条信息,那么Money对象通常是一个不可变对象。这并非意味着薪资不能变,而是意味着:如果要改变薪资,就需要使用另一个Money对象来取代现有的Money对象,而不是在现有的Money对象上修改。你和Money对象之间的关系可以改变,但是Money对象自身不能改变。
Mechanics:
1.检查重构目标是否为不可变对象,或是否可修改为不可变对象。
如果该对象目前还不是不可变的,就使用Remove Setting Method,直到它成为不可变为止。
如果无法将该对象修改为不可变的,就放弃使用本项重构。
2.建立equals() 和hashCode().
3.编译,测试。
4.考虑是否可以删除工厂函数,并将构造函数声明为public。
范例
我们从一个表示“货币种类”的Currency类开始:
public class Currency
{
private String code;
public String getCode()
{
return code;
}
private Currency( String code )
{
this.code = code;
}
}
这个类所做的就是保存并返回一个货币种类代码。它是一个引用对象,所以如果要得到它的实例,必须这么做:
Currency usd = Currency.get("USD");
Currency类维护一个包含所有Currency实例的链表。我们不能直接使用构造函数创建实例,因为Currency构造函数是private的。
要把一个引用对象变成值对象,关键动作是:检查它是否可变。如果不是,就不能使用本项重构,因为可变的值对象会造成烦人的别名问题。
在这里,Currency对象是不可变的,所以下一步就是为它定义equals();
public boolean equals( Object arg )
{
if( !( arg instanceof Currency ) )
{
return false;
}
Currency other = ( Currency ) arg;
return ( code.equals( other.getCode() ) );
}
定义了equals(),就必须同时定义hashcode()。实现hashcode() 有个简单办法:读取equals()使用的所有字段的hash码,然后对它们进行按位异或(^)操作。本例中,这很容易实现,因为equals()只是用了一个字段:
public int hashCode()
{
return code.hashCode();
}
完成这两个函数后,我们可以编译并测试。这两个函数的修改必须同时进行,否则依赖hash的任何集合对象(例如Hashtable、HashSet和HashMap)都可能产生意外行为。
现在,我们可以创建任意多个Currency对象,还可以把构造函数声明为public,直接以构造函数获取Currency实例,从而去掉Currency类中的工厂函数和控制实例创建行为。