![]()
去年面了17個Flutter候選人,問Clean Architecture能畫對三層結構的不到一半。不是不知道Domain層要獨立,是說不清為什么Data層能依賴Domain,反過來卻不行。
這題其實考的是依賴方向,不是背誦。Robert C. Martin(Uncle Bob)在2012年提出Clean Architecture時,核心就一條規則:源代碼依賴只能向內指向。Domain層是同心圓最內層,它不能知道外面任何一層的存在。
但很多人面試時把Repository接口放在Data層,這就是盲區。接口定義是契約,契約必須由內層持有。Data層的Repository實現(Repository Implementation)去依賴Domain層的抽象接口,這才是正確方向。
三層結構的面試陷阱
看一個典型的Flutter項目結構。Domain層包含Entities、Repository抽象接口、Use Cases;Data層放Models、Data Sources、Repository的具體實現;Presentation層管UI、狀態管理(Bloc/Provider/Riverpod)。
面試常掛的第二個盲區:Use Case到底是什么?
它不是"調用API的地方",而是封裝單一業務邏輯的類。每個Use Case只做一件事——獲取用戶資料、更新購物車、驗證優惠券。輸入參數和返回類型都要顯式定義,通常用泛型抽象基類統一約束。
第三個盲區更隱蔽:Error Handling怎么跨層傳遞?
Domain層不能依賴Flutter的http包,也不能用Dio的異常類型。必須自定義Failure類,Data層把技術異常(DioError、SocketException)映射為業務失敗(ServerFailure、CacheFailure)。Presentation層只認識Failure,不認識DioError。
為什么Repository必須是抽象接口
講個類比。Domain層是餐廳菜單,Data層是后廚。顧客(Presentation層)點菜只看菜單,不關心后廚用燃氣灶還是電磁爐。但菜單本身不能寫"用燃氣灶做",那是后廚的事。
Repository抽象接口就是菜單上的菜品描述。具體用Firebase還是REST API實現,是Data層的自由。這種隔離讓單元測試能直接Mock Repository,不用啟動真實數據庫。
面試時問"為什么要用接口",答"方便替換實現"只是60分。高分答案是:Domain層定義業務需要什么,Data層決定技術怎么給。需求變更時,改接口是架構破壞,改實現是正常迭代。
Dependency Injection的實戰選擇
Flutter社區主流用get_it配合injectable代碼生成,或者直接用Riverpod的Provider。面試不會問你背API,會問"為什么不用new關鍵字直接創建對象"。
答案是控制反轉(Inversion of Control)。對象生命周期由容器管理,構造函數的依賴由外部注入。這樣測試時可以換Mock實現,生產環境用真實實現,不用改業務代碼。
injectable的好處是編譯時代碼生成,零運行時反射。這在Flutter里很重要,因為反射(dart:mirrors)在Release模式被禁用,會影響包體積和啟動速度。
SOLID原則在Flutter里的具體落地
Single Responsibility:一個Widget只做一件事。不是"登錄頁面"一個文件2000行,而是拆成LoginForm、LoginButton、ErrorMessage三個獨立Widget。
Open/Closed:用擴展方法給String加驗證邏輯,而不是改原來的類。Flutter 3.0的extension語法讓這變得干凈。
Liskov Substitution:Bloc的狀態類設計。AuthState是基類,AuthLoading、AuthSuccess、AuthError是子類。UI層用switch-case處理,任何子類都能替換基類位置。
Interface Segregation:不要一個Repository接口塞20個方法。按功能拆成UserRepository、OrderRepository、PaymentRepository。調用方只依賴自己需要的接口。
Dependency Inversion:前面說的Domain層持有接口,Data層實現接口。高層模塊(Use Case)不依賴低層模塊(Dio Client),兩者都依賴抽象。
設計模式的面試高頻考點
Singleton在Flutter里慎用。GetIt默認單例,但BLoC通常用Factory,保證每個頁面獨立狀態。面試問"Singleton有什么問題",要答全局狀態難測試、生命周期難管理。
Factory Pattern的典型場景是平臺適配。創建PlatformChannel時,根據Platform.isAndroid返回Android實現,iOS返回iOS實現。調用方無感知。
Observer Pattern就是Flutter的NotificationListener、ScrollNotification,或者BLoC的Stream。面試常問"BLoC和Provider區別",本質是Observer和InheritedWidget兩種狀態傳播機制的差異。
Builder Pattern在Flutter里無處不在。AlertDialog.builder、showModalBottomSheet的builder參數,都是把構造邏輯延遲到需要時執行。復雜對象的 step-by-step 構造,避免構造函數參數爆炸。
Strategy Pattern用于替換算法。圖片加載用CachedNetworkImageStrategy,測試用MockImageStrategy,都實現同一個ImageLoader接口。運行時切換,不用if-else堆邏輯。
Decorator Pattern在Dio的Interceptor里體現。日志攔截器、重試攔截器、Token刷新攔截器,層層包裝原始請求。每個攔截器只加一個職責,符合Single Responsibility。
Adapter Pattern解決接口不兼容。第三方SDK返回的數據結構和你定義的Entity不同,寫個Adapter做字段映射。不是改SDK,也不是改Entity,中間加一層轉換。
Command Pattern把請求封裝成對象。Undo/Redo功能、離線操作的隊列緩存,都是典型場景。Flutter里用Isolate執行耗時Command,避免阻塞UI線程。
這套架構體系是14篇面試系列的第5篇。前面4篇覆蓋了Dart基礎、Widget原理、狀態管理、性能優化,后面還有9篇講測試、部署、混合開發。完整路線圖在GitHub上能搜到,作者叫Mangirdas Kazlauskas,立陶宛的Flutter GDE。
他有個觀點挺有意思:Clean Architecture在Flutter里的最大敵人,是Hot Reload。快速迭代讓人懶得分層,原型直接堆在main.dart。但產品上線后,技術債的利息比銀行還高。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.