你有一筆資料項(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對象。馬上你就可以看到這個例子。