ViewModel 重構(gòu)檢查清單
識(shí)別壞味道:
1.直接訪問(wèn)全局變量(kIsVip)
2.一個(gè)類承擔(dān)了太多職責(zé)(數(shù)據(jù)處理,插入廣告,Vip狀態(tài))
3.函數(shù)長(zhǎng)度超過(guò) 50 行
4.太多 mutable var(狀態(tài)混亂)
5.直接使用單例 (BannerADHelp.shared、MainDataHelp.shared)
提取Input/Output:
定義 Input(所有外部傳入的依賴)
定義 Output(View 需要顯示的數(shù)據(jù))
定義 ViewModel
定義協(xié)議(依賴倒置)
黃金法則: 每個(gè)外部依賴都要有協(xié)議
?3.1 列出所有外部依賴 (BannerADHelp.shared??Device.isIpad?kIsVip)
3.2 為每個(gè)創(chuàng)建協(xié)議
protocol AdServiceProtocol {
? ? func canRequestAD(for enter: ADEnterType) -> Bool
? ? func getAdModel() -> ComponentADModel
}
// 3.3 創(chuàng)建真實(shí)實(shí)現(xiàn)(包裝原有代碼)
class DefaultAdService: AdServiceProtocol {
? ? func canRequestAD(for enter: ADEnterType) -> Bool {
? ? ? ? BannerADHelp.shared.canRequestAD(enter: enter)? // ?? 只在這里調(diào)用
? ? }
}
處理?xiàng)l件判斷?
壞味道就是一堆 if-else

編寫測(cè)試
測(cè)試是重構(gòu)的保障!
// 6.1 創(chuàng)建 Mock
class MockAdService: AdServiceProtocol {
? ? var canRequestADResult = false
? ? func canRequestAD(for enter: ADEnterType) -> Bool {
? ? ? ? return canRequestADResult
? ? }
}
使用:
let mockAd = MockAdService()
mockAd.canRequestADResult = true
?? 記住這個(gè)口訣
找出依賴 → 定義協(xié)議
創(chuàng)建 Input → 注入依賴
分離職責(zé) → 一個(gè)類做一件事
計(jì)算屬性 → 替代存儲(chǔ)屬性
寫測(cè)試 → 驗(yàn)證重構(gòu)正確
最后建議
不要一次全改:先抽最簡(jiǎn)單的依賴(如Device.isIpad)
寫一個(gè)測(cè)試用例:確保重構(gòu)后功能不變
逐步替換:先用新 ViewModel 在簡(jiǎn)單頁(yè)面
團(tuán)隊(duì)同步:和大家分享你的重構(gòu)模式
核心思想:ViewModel 應(yīng)該像函數(shù)一樣——輸入明確,輸出明確,不依賴全局狀態(tài)。每次寫 ViewModel 時(shí)問(wèn)問(wèn)自己:“這個(gè)類能在測(cè)試中單獨(dú)運(yùn)行嗎?”