🎯 什麼情境該想到我
當你「想單獨對付某塊程式碼,卻發現全域變數擋在你的路上」的時候。(p.313)
書上對全域變數的害處沒有再展開,只回頭指向引入靜態設置方法那一節「作了相當完整的敘述」(p.313)。這裡談的是解法:「要想解開一個類別裡面對全域變數的依賴,方法之一是在該類別中為每個相應的全域變數引入一個獲取方法。有了這些獲取方法,便可以通過子類化並重寫方法(314 頁)來令它們返回測試用物件了。不過也有些情況下你可能會需要動用介面提取(285 頁)。」(p.313)
靜態方法呼叫也算全域引用。 書中用一個 Java 例子舉證:RegisterSale 透過 Inventory 類別上的靜態方法取得庫存物件。Feathers 自問自答:「『什麼?』你可能會嚷起來『這不明明是個靜態方法呼叫嗎?』沒錯。但從我們的意圖(測試)來說,它其實就相當於一個全域變數。在 Java 中,該類別本身就是一個全域物件,而且似乎它還需要引用一些狀態才能完成它的工作。」(p.313)
⚙️ 怎麼用(步驟)
書中列出的步驟(p.314):
- 找出你想要替換的全域引用。
- 給它編寫一個相應的獲取方法。確保該獲取方法的存取權限允許你在派生類別中重寫它。
- 將對全域物件的引用替換為對該獲取方法的呼叫。
- 建立測試子類別並重寫獲取方法。
書中示範時特別提醒:「注意,我們將該方法設為受保護的,這樣才能在測試子類別中重寫它。」(p.313)
書中的完整流程還多做了一步:因為被引用的 Inventory 是個單例類別,「因此需要先降低其建構函式的存取權限為受保護的」,才能從它派生出一個 FakeInventory 放進測試用的邏輯;最後才寫 RegisterSale 的測試用子類別,在裡面回傳這個假物件。(p.314)
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- 這一招本身不完整,要靠子類化並重寫方法收尾。 書上明講獲取方法只是鋪路,真正的替換發生在測試子類別重寫它的時候(p.313)。如果你的語言/情境沒辦法做子類化並重寫方法,這招就沒有下半場。
- 獲取方法得是可重寫的存取權限。 步驟第 2 步直接把「確保該獲取方法的存取權限允許你在派生類別中重寫它」寫進去(p.314)——也就是說你會為了測試而放寬原本的封裝(書中示範是設為受保護的)。這是為了能測而付出的設計代價。
- 有時候獲取方法還不夠。 「不過也有些情況下你可能會需要動用介面提取(285 頁)。」(p.313)
- 單例會逼你動它的建構函式。 書中的例子必須把
Inventory的建構函式從私有降為受保護的才能派生出假物件(p.314)——等於為了測試在產品程式碼上開了一個口。