尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
Linux編程
點擊右側關注,免費入門到精通!
作者丨KunMinXhttps://www.jianshu.com/p/9ef813d5c1af
前言
你準備好了嗎?本次列車開往 「重構」 之巔,時速 900km/s。風太大聽不見,但我就是可以很簡單很直的,給你講述事物本質和解決方案!⚡
項目常用架構比對
以下,對常見的 MVC、MVP、Clean、AAC 架構做個比對。
首先,一張表格展示各架構的類冗餘情況:
需求是,寫三個頁面,ListFragment、DetailFragment、PreviewFragment,每個頁面至少用到 3個 Note 業務、3個 User 業務。問:上述架構分別需編寫多少類?
MVC 架構的缺陷
-
View、Controller、Model 相互依賴,造成代碼耦合。
-
難以分工,難以將 View、Controller、Model 分給不同的人寫。
-
難以維護,沒有中間件接口做緩沖,難以替換底層的做到。
publicclassNoteListFragmentextendsBaseFragment{...publicvoidrefreshList(){newThread(newRunnable(){@Overridepublicvoidrun(){//view中直接依賴model。那麼view須等model編寫好才能開工。mNoteList=mDataManager.getNoteList();mHandler.sendMessage(REFRESH_LIST,mNoteList);}}).start();}privateHandlermHandler=newHandler(){@OverridepublicvoidhandleMessage(Messagemsg){switch(msg){caseREFRESH_LIST:mAdapter.setList(mNoteList);mAdapter.notifyDataSetChanged();break;default:}}};...}
MVP 架構的特點與局限
MVP 架構的特點是 面向接口編程。在 View、Presenter、Model 之間分別用 中間件接口 做銜接,當有新的底層做到時,能夠無縫替換。
此外,MVP 的 View 和 Model 並不產生依賴,因此可以說是對 View 和 Model 做了代碼解耦。
publicclassNoteListContract{interfaceINoteListView{voidshowDialog(Stringmsg);voidshowTip(Stringtip);voidrefreshList(List<NoteBean>beans);}interfaceINoteListPresenter{voidrequestNotes(Stringtype);voidupdateNotes(NoteBean...beans);voiddeleteNotes(NoteBean...beans);}interfaceINoteListModel{List<NoteBean>getNoteList();intupdateNote(NoteBeanbean);intdeleteNote(NoteBeanbean);}}
但 MVP 架構有其局限性。按我的理解,MVP 設計的初衷是, 「讓天下沒有難替換的 View 和 Model」 。該初衷背後所基於的假設是,「上層邏輯穩定,但底層做到更替頻繁」 。在這個假設的引導下,使得三者中, 只有 Presenter 具備獨立意志和決定權,掌管著 UI 邏輯和業務邏輯,而 View 和 Model 只是外接的工具。
publicclassNoteListPresenterimplementsNoteListContract.INoteListPresenter{privateNoteListContract.INoteListModelmDataManager;privateNoteListContract.INoteListViewmView;@OverridepublicvoidrequestNotes(Stringtype){Observable.create(newObservableOnSubscribe<List<NoteBean>>(){@Overridepublicvoidsubscribe(ObservableEmitter<List<NoteBean>>e)throwsException{List<NoteBean>noteBeans=mDataManager.getNoteList();e.onNext(noteBeans);}}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(newConsumer<List<NoteBean>>(){@Overridepublicvoidaccept(List<NoteBean>beans)throwsException{//presenter直接干預了UI在拿到數據後做什麼,使得邏輯上沒有發生解耦。//正常來說,解耦意味著,presenter的職能邊界僅限返回結果數據,//由UI來依據響應碼處理UI邏輯。mView.refreshList(beans);}});}...}
然而,這樣的假設多數時候並不實際。可視化需求是變化多端的,在牽涉到視覺交互時,必然涉及 UI 邏輯的修改,也就是說,View 和 Presenter 的相互牽連,使得 UI 的改動需要 View 和 Presenter 編寫者配合著完成,增加溝通協作成本。
長久來看,二者都難以成長。Presenter 編寫者容易被各種非本職工作拖累,View 的編寫者不會嘗試獨立自主,例如通過多態等模式將 UI 封裝成可適應性的組件,反正 … 有 Presenter 來各種 if else 嘛。
Clean 架構的特點和不足
為解決 Presenter 職能邊界不明確 的問題,在 Clean 架構中,業務邏輯的職能被轉移到領域層,由 Usecase 專職管理。Presenter 則弱化為 ViewModel ,作為代理數據請求,和銜接數據回調的緩沖區。
Clean 架構的特點是 單向依賴、數據驅動編程。 View -> ViewModel -> Usecase -> Model 。
View 對 ViewModel 的單向依賴,是通過 databinding 特性做到的。ViewModel 只負責代理數據請求,在 Usecase 處理完業務返回結果數據時,結果數據被賦值給可觀察的 databinding 數據,而 View 則依據數據的變化而變化。
publicclassNoteListViewModel{privateObservableList<NoteBean>mListObservable=newObservableArrayList<>();privatevoidrequestNotes(Stringtype){if(null==mRequestNotesUsecase){mRequestNotesUsecase=newProveListInitUseCase();}mUseCaseHandler.execute(mRequestNotesUsecase,newRequestNotesUsecase.RequestValues(type),newUseCase.UseCaseCallback<RequestNotesUsecase.ResponseValue>(){@OverridepublicvoidonSuccess(RequestNotesUsecase.ResponseValueresponse){//viewModel的可觀察數據發生變化後,databinding會自動更新UI展示。mListObservable.clear();mListObservable.addAll(response.getNotes());}@OverridepublicvoidonError(){}});}...}
但 Clean 架構也有不足:粒度太細 。一個 Usecase 受限於請求參數,因而只能處理一類請求。View 請求的數據包含幾種類型,就至少需要準備幾個 Usecase。Usecase 是依據當前 View 對數據的需求量身定制的,因此 Usecase 的復用率極低,項目會因而急劇的增加類和重復代碼。
publicclassRequestNotesUseCaseextendsUseCase<RequestNotesUseCase.RequestValues,RequestNotesUseCase.ResponseValue>{privateDataManagermDataManager;@OverrideprotectedvoidexecuteUseCase(finalRequestValuesvalues){List<NoteBean>noteBeans=mDataManager.getNotes();...getUseCaseCallback().onSuccess(newRequestNotesUseCase.ResponseValue(noteBeans));}//每新建一個usecase類,都需要手動為其配置請求參數列表和響應參數列表。publicstaticfinalclassRequestValuesimplementsUseCase.RequestValues{privateStringtype;publicStringgetType(){returntype;}publicvoidsetType(Stringtype){this.type=type;}}publicstaticfinalclassResponseValueimplementsUseCase.ResponseValue{publicList<NoteBean>mBeans;publicResponseValue(List<NoteBean>beans){mBeans=beans;}}}
AAC 架構的特點
AAC 也是數據驅動編程。只不過它不依賴於 MVVM 特性,而是直接在 View 中寫個觀察者回調,以接收結果數據並處理 UI 邏輯。
publicclassNoteListFragmentextendsBaseFragment{@OverridepublicvoidonActivityCreated(@NullableBundlesavedInstanceState){super.onActivityCreated(savedInstanceState);viewModel.getNote().observe(this,newObserver<NoteBean>(){@OverridepublicvoidonChanged(@NullableNoteBeanbean){//updateUI}});}...}
你完全可以將其理解為 B/S 架構:從 Web 前端向 Web 後端發送了數據請求,後端在處理完畢後響應結果數據給前端,前端再依據需求處理 UI 邏輯。等於說, AAC 將業務完全壓到了 Model 層。
ViaBus 架構的由來及特點
上一輪重構項目在用 Clean 架構,為此我決定跳過 AAC,基於對移動端數據交互的理解,編寫「消息驅動編程」架構。
由於借助總線來代理數據的請求和響應,因此取名 ViaBus。
不同於以往的架構,ViaBus 明確界定了什麼是 UI,什麼是業務。
UI 的作用是視覺交互,為此 UI 的職責範圍是請求數據和處理 UI 邏輯 。業務的作用是供應數據,因此 業務的職責範圍是接收請求、處理數據、返回結果數據 。
UI 不需要知道數據是怎麼來的、通過誰來的,它只需向 bus 發送一個請求,如果有業務註冊了該類 「請求處理者」,那麼自然有人來處理。業務也無需知道 UI 在拿到數據後會怎麼用,它只需向 bus 回傳結果,如果有 UI 註冊了「觀察響應者」,那麼自然有人接收,並依據響應碼行事。
這樣,在靜態 bus 的加持下,UI 和業務是完全解耦的,從根本上解決了相互牽連的問題。此外,不同於上述架構的每個 View 都要對應一個 Presenter 或 ViewModel,在 ViaBus 中,一個模塊中的 UI 可以共享多個「業務處理者」實例,使 代碼的復用率提升到100%。
ViaBus 現已在 Github 開源,歡迎 Star & Fork ~
推薦↓↓↓
長
按
關
註
?【16個技術公眾號】都在這里!
涵蓋:工程師大咖、源碼共讀、工程師共讀、數據結構與算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默工程師等。