章節概述#
待辦清單上還有什麼能幫助消除子類別?本章引入 currency(貨幣) 的概念。透過逐步重構,讓兩個子類別的建構子趨於一致,最終將共同實作推到父類別 Money。
引入 Currency#
從測試開始——我們希望每個 Money 物件都能回報自己的貨幣:
public void testCurrency() {
assertEquals("USD", Money.dollar(1).currency());
assertEquals("CHF", Money.franc(1).currency());
}先在 Money 宣告抽象方法,然後在兩個子類別中實作:
// Money
abstract String currency();
// Dollar
String currency() {
return "USD";
}
// Franc
String currency() {
return "CHF";
}用實例變數取代常數#
為了讓兩個子類別的 currency() 實作統一,將貨幣字串改為存在實例變數中:
// Franc
private String currency;
Franc(int amount) {
this.amount = amount;
currency = "CHF";
}
String currency() {
return currency;
}Dollar 也做同樣處理。兩邊的 currency 欄位和 currency() 方法現在完全一致,可以推到父類別:
// Money
protected String currency;
String currency() {
return currency;
}統一建構子#
下一步是讓建構子也一致。策略是把貨幣常數字串移到工廠方法中,讓建構子接受 currency 參數:
// Franc
Franc(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}工廠方法傳入貨幣字串:
// Money
static Money franc(int amount) {
return new Franc(amount, "CHF");
}插曲:修正 times() 中的直接呼叫#
在重構過程中,作者注意到 Franc.times() 直接呼叫建構子而不是工廠方法。雖然不在當前的重構範圍內,但作者允許自己做一個短暫的中斷——修正 times() 讓它使用工廠方法:
// Franc
Money times(int multiplier) {
return Money.franc(amount * multiplier);
}技巧: 作者引用 Jim Coplien 的規則——可以接受短暫的中斷,但絕不中斷一個中斷。這是在紀律與務實之間取得平衡的好原則。
接著對 Dollar 做同樣的修改,這次一步到位:
// Money
static Money dollar(int amount) {
return new Dollar(amount, "USD");
}
// Dollar
Dollar(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
Money times(int multiplier) {
return Money.dollar(amount * multiplier);
}一次成功,沒有出錯。
推上共同建構子#
兩個子類別的建構子現在完全一致,可以推到 Money:
// Money
Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// Franc
Franc(int amount, String currency) {
super(amount, currency);
}
// Dollar
Dollar(int amount, String currency) {
super(amount, currency);
}TDD 的步幅調節#
重點: TDD 是一個轉向的過程(steering process)——覺得小步驟太束縛?放大步幅。覺得有點不確定?縮小步幅。沒有永遠正確的步幅大小。作者自己在本章就是先嘗試大步,犯了一個愚蠢錯誤,回退一分鐘的修改,切換到更小的步幅重新來過。
本章小結#
- 在大設計想法上有點卡住時,先處理之前注意到的小事(引入 currency)
- 透過將差異移到呼叫端(工廠方法),讓兩個建構子趨於一致
- 在重構過程中發現小問題(
times()直接呼叫建構子),允許短暫中斷來修正 - 對 Franc 做的修改,在 Dollar 上以更大的步幅重複一次
- 將完全一致的建構子推上父類別