🎯 什麼情境該想到我
當你「想抽出一個介面,卻發現最適合的那個名字已經被現有的類別用掉了,而手上又沒有自動重構工具」的時候。
書中 p.281 開場就是這個困境:「介面提取(書 p.285)是個有用且用起來既方便又順手的技術,但它也有其困難之處:命名。我常常遇到這樣的情況:我想要提取出一個介面,然而卻發現我想給它取的名字已經被當前類給占用了。這時如果我的 IDE 支援重新命名類或介面提取的話,情況就會很簡單。但如果不幸不支援,那麼就只能退而求其次了。」
同頁給出解法:「如果一個類的名字恰恰適合用來作你的介面名,而且你手頭又沒有自動重構工具的話,可以使用實現提取手法來獲得所需的分離。提取一個類的實現時,只需從它派生出一個新類,並將其中的所有具體方法都塞到這個衍生類別中,也就是說把它架空。」
⚙️ 怎麼用(步驟)
書中 p.283「25.9.1 步驟」小節:
- 將目標類的宣告複製一份,給複製的類起一個新名字。這裡最好建立一個命名習慣——書中說作者通常使用的就是添加 “Production” 前綴以表明這是一個產品實現。
- 將目標類變成一個介面,這一步通過刪除所有非公有方法和資料成員來實現。
- 將所有剩下來的公有方法設為抽象方法。如果你的語言是 C++,則還需確保你設成抽象的那些方法都是被虛擬方法重寫的。
- 刪除該介面類檔案當中的不必要的 import 或 include,往往有許多都可以刪掉。可以依靠編譯器(書 p.251)來做這一步:挨個刪除 import/include,看編譯出不出錯,出錯了就添回去,否則就刪掉。
- 讓你的產品類實現該介面。
- 編譯你的產品類以確保新介面中的所有方法都被實現了。
- 編譯系統的其餘部分,找出那些建立原類的物件的地方,將它們修改為建立新的產品類的物件。
- 重編譯並測試。
C++ 額外提醒(p.282):把類架空成純粹的介面類之後,「別忘了加上純虛析構函式,並在一個實現檔案中定義它」——譯者註說明原因是衍生類別的析構函式總是要呼叫基類的析構函式,不管基類是不是抽象類。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- 英文名照原書印。 中譯本 p.281 印作「實現提取(Extract Implemeter)」,
Implemeter疑為Implementer的排版脫字;此處照原書寫,搜尋時兩種拼法都試一下。 - 做完之後依賴其實還沒改善。 p.283 講得非常誠實:「好,到目前為止我們的重構只是將程式碼中建立某具體類的物件的地方改為建立另一個具體類的物件,這對系統中的依賴情形並沒有任何改善。」真正的收穫是後續機會——「然而,由於有了介面類⋯⋯的存在,現在我們便可以考察所有那些建立⋯⋯物件的地方,看看是否可以適當運用工廠方法來進一步減少依賴了。」
- 有基類或衍生類別時會變得很麻煩。 p.284:「如果目標類沒有任何基類或衍生類別,則實現提取用起來還是相對簡單的;否則就需要聰明一點了。」同頁說明限制:「實現提取需要一個能被轉換為介面的具體類。並且在完成提取之後你會得到一個介面和一個具體類。」書中示範的做法是把新的產品類硬塞進既有的繼承體系中間。
- 塞進繼承體系就該考慮換招。 p.285 直接勸退:「如果你發現自己將一個類像上面這樣嵌入了繼承體系,那麼我建議你真的需要考慮一下是否應當改用介面提取,並給你的介面選擇其他名字了。介面提取比實現提取要直接得多。」
- 不要用 “I” 前綴當偷懶解法。 p.281:「通常我不贊成往當前類的名字前草草加上一個 ‘I’ 前綴就了事的做法,除非這種做法已經是你的程式碼基裡面的命名慣例。」理由是一個程式碼基裡差不多一半名字帶 “I” 前綴、另一半不帶,用到型別名字時有一半機率會用錯。同頁邊註強調:「命名是設計的關鍵部分。好的名字有助於人們理解系統⋯⋯反之,糟糕的名字則會影響理解,並給你身後的程式設計師帶來無盡煩惱。」
- p.287 的「介面命名」附註把兩者的取捨講明:把資料和方法全轉移到新的衍生類別、把原類架空成介面,好處是「你無需將程式碼中所有引用⋯⋯的地方修改成引用新的介面」,缺點是「將所有資料成員和方法統統轉移到另一個類中需要不少步驟」;作者說「只要風險足夠小,我還是會時不時用這招的。這其實就是所謂的實現提取」。
🔗 相關工具
- 工具-打破依賴以便測試 — 這條技術屬於的大類
- 介面提取 — 直接的替代方案;書 p.285 明講「介面提取比實現提取要直接得多」
- 提取並重寫工廠方法 — p.283 建議做完實現提取後接著考察建立處,用工廠方法進一步減少依賴
- 回連 修改程式碼的藝術