章節概述#

本章完成了消除 DollarFranc 子類別的最後一步。既然 times() 已在上一章統一到 Money,子類別裡只剩建構子,沒有存在的理由了。同時也清理了因舊架構而存在的冗餘測試。

刪除子類別#

DollarFranc 只剩下建構子,這不足以作為保留子類別的理由。做法是將工廠方法中對子類別的參考改為直接使用 Money

// 原本的 Franc 工廠方法
static Money franc(int amount) {
    return new Money(amount, "CHF");
}

// 原本的 Dollar 工廠方法
static Money dollar(int amount) {
    return new Money(amount, "USD");
}

Dollar 沒有任何其他參考了,可以直接刪除。Franc 還有一個參考留在測試中:

public void testDifferentClassEquality() {
    assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));
}

清理冗餘測試#

問題是:其他地方的測試是否已經充分覆蓋了 equality?看看現有的 equality 測試:

public void testEquality() {
    assertTrue(Money.dollar(5).equals(Money.dollar(5)));
    assertFalse(Money.dollar(5).equals(Money.dollar(6)));
    assertTrue(Money.franc(5).equals(Money.franc(5)));
    assertFalse(Money.franc(5).equals(Money.franc(6)));
    assertFalse(Money.franc(5).equals(Money.dollar(5)));
}

現在 Money.franc()Money.dollar() 都回傳 Money 實例,第三和第四個斷言與前兩個斷言是重複的(都在測試同一個 Money.equals() 邏輯),可以簡化為:

public void testEquality() {
    assertTrue(Money.dollar(5).equals(Money.dollar(5)));
    assertFalse(Money.dollar(5).equals(Money.dollar(6)));
    assertFalse(Money.franc(5).equals(Money.dollar(5)));
}

重點: testDifferentClassEquality 這個測試是為了確保「不同類別但相同幣別」能正確比較——但如果我們正在消除子類別,一個保證「子類別存在時系統能運作」的測試就不再是助力,而是負擔。刪除它,順便刪除 Franc 類別。

同樣的邏輯也適用於 testFrancMultiplication():既然程式碼中已經沒有基於幣別的不同邏輯(那是雙類別時代的事了),這個測試也可以刪除而不損失任何信心。

本章小結#

  • 完成了子類別的清除,將 DollarFranc 全部刪除,只留下單一的 Money 類別
  • 隨著程式碼結構的改變,測試也需要跟著調整——在舊架構下有意義的測試,在新架構下可能是冗餘甚至有害的
  • 系統準備好進入下一個大主題:加法運算