重构改善既有代码设计--重构手法19:Replace Data Value with Object (以对象取代数据值)
你有一笔数据项(data item),需要额外的数据和行为。
将这笔数据项变成一个对象。
class Order...
private string customer;
==>
class Order...
private Customer _customer;
class Customer...
private string _name;
动机
一开始你可能会用一个字符串来表示[电话号码]概念,但是随后你就会发现,电话号码需要[格式化]、[抽取区号]之类的特殊行为。当这些臭味开始出现,你就应该将数据值(data value)变成对象(object)。
作法
1. 为[待替换数值]新建一个class,在其中声明一个final值域,其型别和source class中的[待替换数值]型别一样。然后在新class中加入这个值域的取值函数(getter),再加上一个[接受此值域为参数]的构造函数。
2. 编译。
3. 将source class中的[待替换数值值域]的型别改为上述的新建class。
4. 修改source class中此一值域的取值函数(getter),令它调用新建class的取值函数。
5. 如果source class构造函数中提及这个[待替换值域](多半是赋值动作),我们就修改构造函数,令它改用新class的构造函数来对值域进行赋值动作。
6. 修改source class中[待替换值域]的设值函数(setter),令它为新class创建一个实体。
7. 编译,测试。
8. 现在,你有可能需要对新class使用Change Value to Reference(179)。
下面有一个代表[定单]的Order class,其中以一个字符串记录定单客户。现在,我希望改为以一个对象来表示客户信息,这样我就有充裕的弹性保存客户地址、信用等级等等信息,也得以安置这些信息的操作行为。Order class最初如下:
class Order...
public Order(String customer) {
_customer = cusomer;
}
public String getCustomer() {
return _customer;
}
public void setCustomer(String arg) {
_customer = arg;
}
private String _customer;
Order class的客户代码可能像下面这样:
private static int numberOfOrdersFor(Collection orders, String customer) {
int result = 0;
Iterator iter = orders.iterator();
while(iter.hasNext()) {
Order each = (Order)iter.next();
if(each.getCustomer().equals(customer)) result ++;
}
return result;
}
首先,我要新建一个Customer class来表示[客户]概念。然后在这个class中建立一个final值域,用以保存一个字符串,这是Order class目前所使用的。我将这个新值域命名为_name,因为这个字符串的用途就是记录客户名称。此外我还要为这个字符串加上取值函数(getter)和构造函数(constructor)。
class Customer {
public Customer(String name) {
_name = name;
}
public String getName() {
return _name;
}
private final String _name;
}
现在,我要将Order中的_customer值域的型别修改为Customer;并修改所有引用此一值域的函数,让它们恰当地改而引用Customer实体。其中取值函数和构造函数的修改都很简单;至于设值函数(setter),我让它创建一份Customer实体。
class Order...
public Order(String customer) {
_customer = new Customer(customer);
}
public String getCustomer() {
return _customer.getName();
}
public void setCustomer(String arg) {
_customer = new Customer(arg);
}
private Customer _customer;
设值函数需要创建一份Customer实体,这是因为以前的字符串是个实值对象(value object),所以现在的Customer对象也应该是个实值对象。这也就意味每个Order对象都包含自己的一个Customer对象。注意这样一条规则:实值对象应该是不可修改内容的--这便可以避免一些讨厌的[别名](aliasing)错误。日后或许我会想让Customer对象成为引用对象(reference object),但那是另一项重构手法的责任。现在我可以编译并测试了。
public String getCustomerName() {
return _customer.getName();
}
至于构造函数和设值函数,我就不必修改其签名(signature)了,但参数名称得改:
public Order(String customerName) {
_customer = new Customer(customerName);
}
public void setCustomer(String customerName) {
_customer = new Customer(customerName);
}
本次 重构到此为止。但是,这个案例和其他很多案例一样,还需要一个后续步骤。如果想在Customer中加入信用等级、地址之类的其他信息,现在还做不到,因为目前的Customer还是被作为实值对象(value object)来对待,每个Order对象都拥有自己的Customer对象。为了给Customer class加上信用等级、地址之类的属性,我必须运用Change Value to Reference(179),这么一来属于同一客户的所有Order对象就可以共享同一个Customer对象。马上你就可以看到这个例子。