你有一个数据项,需要与其他数据和行为一起使用才有意义。
开发初期,你往往决定以简单的数据项(data item)表示简单的行为。但是,随着开发的进行,你可能会发现,这些简单数据项不再那么简单了。
比如说,一开始你可能会用一个字符串来表示「电话号码」概念,但是随后你就会发现,电话号码需要「格式化」、「抽取区号」之类的特殊行为。
如果这样的数据项只有一二个,你还可以把相关函数放进数据项所属的对象里头;但是Duplication Code臭味和Feature Envy臭味很快就会从代码中散发出来。当这些臭味开始出现,你就应该将数据值(data value)变成对象(object)
下面有一个代表「定单」的Order class,其中以一个字符串记录定单客户。现在,我希望改用一个对象来表示客户信息,这样我就有充裕的弹性保存客户地址、信用 等级等等信息,也得以安置这些信息的操作行为。Order class最初如下
class Order...public Order (String customer) {_customer = customer;}public String getCustomer() {return _customer;}public void setCustomer(String arg) {_customer = arg;}// 通过属性表示客户信息private String _customer;
调用Order类的代码
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.getCustomerName().equals(customer)) result++;}return result;}
首先,我要新建一个Customer class来表示「客户」概念。
然后在这个class中建立一个final值域,用以保存一个字符串,这是Order class目前所使用的。
我将这个新值域命名为_name,因为这个字符串的用途就是记录客户名称。
此外我还要为这个字符串加上取值函数(getter)和构造函数(constructor)
// 创建一个新类表示用户信息class Customer {// 构造器获取原先Order类中customer属性值public Customer (String name) {_name = name;}// 为属性提供get和set方法public String getName() {return _name;}private final String _name;}
现在,我要将Order中的_customer值域的型别修改为Customer;
并修改所有引用此一值域的函数,让它们恰当地改而使用Customer实体。
其中取值函数和构造函数的修改都很简单;至于设值函数(setter),我让它创建一份Customer实体。
class Order...private Customer _customer;public Order (String customer) {// 将string类型替换为Customer类型对象_customer = new Customer(customer);}// 获取用户信息public String getCustomer() {//改为调用Customer对象获取属性值return _customer.getName();}// 设置用户信息,通过调用Customer构造器设置值public void setCustomer(String arg) {_customer = new Customer(arg);}
设值函数需要创建一个Customer实例,这是因为以前的字符串是个值对象(value object),所以现在的Customer对象也应该是个值对象。
这也就意味每个Order对象都包含自己的一个Customer对象。注意这样一条规则:值对象应该是不可修改内容的——这便可以避免一些讨厌的别名问题。日后或许我会想让Customer对象成为引用对象(reference object),但那是另一项重构手法的责任。现在我可以编译并测试了。
我需要观察Order类中的_customer字段的操作函数,并作出一些修改,使它更好地反映出修改后的新形势。
对于取值函数,我会使用Rename Method (273)改变其名称,让它更清晰地表示,它所返回的是消费者名称,而不是个Customer对象。
// 将getCustomer名称改为getCustomerName 提高阅读性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对象中加入信用等级、地址之类的其他信息,现在还做不到,因为目前的Customer还是被作为值对象(value object)来对待。
下面介绍下只对象和引用对象的区别,就能理解Customer为什么还不能投入生产使用。
值对象
:当一个客户Customer下了多个订单Order后,每个订单类都将创建一个客户对象,即使多个订单属于同一个客户,但每个Order对象还是拥有各自的Customer对象。所以对于多个订单Order来说没办法共享同一个客户的信息。
引用对象
:同 一客户拥有多份不同定单,代表这些定单的所有Order对象就可以共享同一个Customer对象以及对象中的所有属性信息。
下一篇:C#实现身份证校验代码