章節概述#
上一章為了速度而大量複製程式碼,現在是清理的時候了。本章的目標是將 Dollar 和 Franc 中重複的 equals() 提取到共同的父類別 Money 中。策略是找到共同的父類別(common superclass),逐步把共用邏輯往上搬。
建立 Money 父類別#
首先建立一個空的 Money 類別,讓 Dollar 繼承它:
class Money {
}class Dollar extends Money {
private int amount;
}
Figure 6.1: A common superclass for two classes
classDiagram
class Money {
#int amount
+equals(Object) boolean
}
class Dollar {
+Dollar(int amount)
}
class Franc {
+Franc(int amount)
}
Money <|-- Dollar
Money <|-- Franc接著將 amount 從 Dollar 搬到 Money,並將可見性從 private 改為 protected,讓子類別能存取:
class Money {
protected int amount;
}class Dollar extends Money {
}逐步搬移 equals()#
搬移 equals() 的過程分為多個小步驟,每一步都確保測試通過:
- 改變暫時變數的宣告型別:從
Dollar改為Money
public boolean equals(Object object) {
Money dollar = (Dollar) object;
return amount == dollar.amount;
}- 改變型別轉換:從
(Dollar)改為(Money)
public boolean equals(Object object) {
Money dollar = (Money) object;
return amount == dollar.amount;
}- 重新命名暫時變數以提高可讀性:
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount;
}- 將 equals() 搬到 Money:
// Money
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount;
}補寫缺失的測試#
在處理 Franc 之前,作者發現等值性測試中缺少 Franc 對 Franc 的比較。複製程式碼的「罪過」在此顯現——重構時發現測試不足。
注意: 在測試不足的程式碼中實施 TDD 時,你遲早會遇到缺乏測試保護的重構。此時應該先補寫測試,再進行重構。否則可能在重構中引入錯誤而不自知。
public void testEquality() {
assertTrue(new Dollar(5).equals(new Dollar(5)));
assertFalse(new Dollar(5).equals(new Dollar(6)));
assertTrue(new Franc(5).equals(new Franc(5)));
assertFalse(new Franc(5).equals(new Franc(6)));
}作者用了一個誇張的因果鏈來強調這個觀點:不補測試 → 重構出錯 → 害怕重構 → 停止重構 → 設計惡化 → 被開除 → 狗離開你 → 不注意營養 → 牙齒壞掉。所以,為了牙齒健康,重構前請補寫測試。
對 Franc 做同樣的處理#
有了測試保護後,對 Franc 進行與 Dollar 相同的步驟:
- 讓 Franc 繼承 Money
- 刪除 Franc 中的
amount欄位(改用 Money 的) - 逐步將
Franc.equals()改為與Money.equals()完全一致 - 刪除
Franc.equals(),因為它與父類別的實作完全相同
class Franc extends Money {
}測試全部通過,重複的 equals() 已被消除。
新的疑問#
消除 equals() 重複後,一個新問題浮現:如果拿 Franc 和 Dollar 比較會怎樣? 這將在下一章處理。
本章小結#
- 將共用程式碼從 Dollar 逐步搬移到父類別 Money
- 讓 Franc 也成為 Money 的子類別
- 調和(reconcile) 兩個
equals()實作,確認它們完全一致後才刪除冗餘版本 - 補寫了重構前缺失的測試