🎯 什麼情境該想到我
當你「想換掉某個成員變數上的物件,但它是在建構函式裡被建立的,而且你用的語言(例如 C++)不讓你用工廠方法那一招」的時候。
書中 p.278 開頭就把定位講清楚:「提取並重寫工廠方法(書 p.276)在分離對於類型的依賴方面是個強大的手法,但它並不是萬能的。其最大的問題就是不適用於 C++。在 C++ 裡面你無法在基類建構函式中呼叫衍生類別的虛擬函式。但所幸還是有解決辦法的,如果你只在建構函式中建立新物件,並且並不用這個新物件做其他任何事情的話,本節的手法是適用的。」
同頁講原理:「該手法的關鍵在於為你想要替換的成員變數引入一個獲取方法,以便可以通過該獲取方法來換入偽物件。引入了獲取方法之後,將該類中所有使用該物件的地方改為通過獲取方法來獲取。這麼一來,你就可以在衍生類別中通過子類化並重寫該獲取方法來換入測試用的物件了。」
⚙️ 怎麼用(步驟)
書中 p.281「步驟」小節:
- 找出需要為其引入獲取方法的物件。
- 將建立該物件所需的所有邏輯都提取到一個獲取方法中。
- 將所有對該物件的使用都替換為通過該獲取方法來獲取,並在所有建構函式中將該物件的引用初始化為 null(C++ 中則是指標初始化為 0)。
- 在獲取方法裡面加入「首次呼叫時建立」功能,這樣當成員引用為 null 時該獲取方法就會負責建立新物件。
- 子類化該類,重寫這個獲取方法並在其中提供你自己的測試用物件。
p.279 用一句話總結前幾步:「我們所做的第一件事情就是引入一個遲求值的獲取方法,該方法會在第一次被呼叫的時候建立⋯⋯物件。然後我們將該類裡面所有用到該物件的地方都改為通過呼叫這個獲取方法來獲得它。」
p.280 邊註解釋什麼叫遲求值:「一個遲求值的獲取方法在呼叫方看來與其他獲取方法並沒什麼兩樣。但有一點關鍵的區別,就是遲求值的獲取方法在第一次被呼叫的時候才去建立被返回的物件。」同段並指出「遲求值的獲取方法也用在單件模式中」。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- 未初始化就被存取的風險。 p.280:「提取並重寫獲取方法手法的缺點之一便是問題成員變數可能在未被初始化之前就被不小心存取到。所以最好確保類裡面的所有用到該成員變數的地方都是通過我們的獲取方法來存取的。」
- 物件生命週期要格外小心。 p.280:「使用提取並重寫獲取方法手法時,你須得對物件的生命週期格外小心,尤其是對於像 C++ 這樣的沒有內建垃圾收集的語言。確保你釋放測試用物件的方式與產品程式碼釋放產品用物件的方式是一致的。」
- 作者自己不常用。 p.280:「其實我並不常用這一手法。如果我發現問題物件上只有唯一一個方法的話,使用提取並重寫呼叫(書 p.275)會容易得多。但如果同一物件上有多個問題方法的話,提取並重寫獲取方法就是更好的選擇了。想想看,只需提取出一個獲取方法然後重寫它,問題就都解決了。」
- 適用前提是窄的:只在建構函式中建立新物件、且不拿這個新物件做其他任何事情(p.278)。