摘要:架構(gòu)的特點與局限架構(gòu)的特點是面向接口編程。架構(gòu)的特點和不足為解決職能邊界不明確的問題,在架構(gòu)中,業(yè)務(wù)邏輯的職能被轉(zhuǎn)移到領(lǐng)域?qū)樱蓪B毠芾怼5淖饔檬且曈X交互,為此的職責(zé)范圍是請求數(shù)據(jù)和處理邏輯。
聲明轉(zhuǎn)載于作者:KunMinX
原文鏈接:https://www.jianshu.com/p/9ef...
前不久剛結(jié)束對 20 模塊項目的第 3 輪重構(gòu),一路見證 MVC、MVP、Clean 的優(yōu)缺點并形成自己的體會。
近期在總結(jié)工作經(jīng)驗的同時,開始寫博客。順便開源了我設(shè)計的 ViaBus 架構(gòu)。
項目地址:項目常用架構(gòu)比對
https://github.com/KunMinX/an...
以下,對常見的 MVC、MVP、Clean、AAC 架構(gòu)做個比對。
首先,一張表格展示各架構(gòu)的類冗余情況:
需求是,寫三個頁面,ListFragment、DetailFragment、PreviewFragment,每個頁面至少用到 3個 Note 業(yè)務(wù)、3個 User 業(yè)務(wù)。問:上述架構(gòu)分別需編寫多少類?
架構(gòu) | 涉及類 | 類總數(shù) |
---|---|---|
MVC | Fragment:3個,Controller:3個,Model:2個 | 8個 |
MVP | Fragment:3個,Presenter:3個,Model:3個,Contract:1個 | 10個 |
Clean | Fragment:3個,ViewModel:3個,Usecase:18個,Model:3個 | 27個 |
AAC | Fragment:3個,ViewModel:3個,Model:3個 | 9個 |
View、Controller、Model 相互依賴,造成代碼耦合。
難以分工,難以將 View、Controller、Model 分給不同的人寫。
難以維護,沒有中間件接口做緩沖,難以替換底層的實現(xiàn)。
public class NoteListFragment extends BaseFragment { ... public void refreshList() { new Thread(new Runnable() { @Override public void run() { //view 中直接依賴 model。那么 view 須等 model 編寫好才能開工。 mNoteList = mDataManager.getNoteList(); mHandler.sendMessage(REFRESH_LIST, mNoteList); } }).start(); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg) { case REFRESH_LIST: mAdapter.setList(mNoteList); mAdapter.notifyDataSetChanged(); break; default: } } }; ... }MVP 架構(gòu)的特點與局限
MVP 架構(gòu)的特點是 面向接口編程。在 View、Presenter、Model 之間分別用 中間件接口 做銜接,當(dāng)有新的底層實現(xiàn)時,能夠無縫替換。
此外,MVP 的 View 和 Model 并不產(chǎn)生依賴,因此可以說是對 View 和 Model 做了代碼解耦。
public class NoteListContract { interface INoteListView { void showDialog(String msg); void showTip(String tip); void refreshList(Listbeans); } interface INoteListPresenter { void requestNotes(String type); void updateNotes(NoteBean... beans); void deleteNotes(NoteBean... beans); } interface INoteListModel { List getNoteList(); int updateNote(NoteBean bean); int deleteNote(NoteBean bean); } }
但 MVP 架構(gòu)有其局限性。按我的理解,MVP 設(shè)計的初衷是, “讓天下沒有難替換的 View 和 Model” 。該初衷背后所基于的假設(shè)是,“上層邏輯穩(wěn)定,但底層實現(xiàn)更替頻繁” 。在這個假設(shè)的引導(dǎo)下,使得三者中, 只有 Presenter 具備獨立意志和決定權(quán),掌管著 UI 邏輯和業(yè)務(wù)邏輯,而 View 和 Model 只是外接的工具。
public class NoteListPresenter implements NoteListContract.INoteListPresenter { private NoteListContract.INoteListModel mDataManager; private NoteListContract.INoteListView mView; @Override public void requestNotes(String type) { Observable.create(new ObservableOnSubscribe>() { @Override public void subscribe(ObservableEmitter
> e) throws Exception { List
noteBeans = mDataManager.getNoteList(); e.onNext(noteBeans); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer >() { @Override public void accept(List
beans) throws Exception { //presenter 直接干預(yù)了 UI 在拿到數(shù)據(jù)后做什么,使得邏輯上沒有發(fā)生解耦。 //正常來說,解耦意味著,presenter 的職能邊界僅限返回結(jié)果數(shù)據(jù), //由 UI 來依據(jù)響應(yīng)碼處理 UI 邏輯。 mView.refreshList(beans); } }); } ... }
然而,這樣的假設(shè)多數(shù)時候并不實際。可視化需求是變化多端的,在牽涉到視覺交互時,必然涉及 UI 邏輯的修改,也就是說,View 和 Presenter 的相互牽連,使得 UI 的改動需要 View 和 Presenter 編寫者配合著完成,增加溝通協(xié)作成本。
長久來看,二者都難以成長。Presenter 編寫者容易被各種非本職工作拖累,View 的編寫者不會嘗試獨立自主,例如通過多態(tài)等模式將 UI 封裝成可適應(yīng)性的組件,反正 ... 有 Presenter 來各種 if else 嘛。
Clean 架構(gòu)的特點和不足為解決 Presenter 職能邊界不明確 的問題,在 Clean 架構(gòu)中,業(yè)務(wù)邏輯的職能被轉(zhuǎn)移到領(lǐng)域?qū)樱?Usecase 專職管理。Presenter 則弱化為 ViewModel ,作為代理數(shù)據(jù)請求,和銜接數(shù)據(jù)回調(diào)的緩沖區(qū)。
Clean 架構(gòu)的特點是 單向依賴、數(shù)據(jù)驅(qū)動編程。 View -> ViewModel -> Usecase -> Model 。
View 對 ViewModel 的單向依賴,是通過 databinding 特性實現(xiàn)的。ViewModel 只負責(zé)代理數(shù)據(jù)請求,在 Usecase 處理完業(yè)務(wù)返回結(jié)果數(shù)據(jù)時,結(jié)果數(shù)據(jù)被賦值給可觀察的 databinding 數(shù)據(jù),而 View 則依據(jù)數(shù)據(jù)的變化而變化。
public class NoteListViewModel { private ObservableListmListObservable = new ObservableArrayList<>(); private void requestNotes(String type) { if (null == mRequestNotesUsecase) { mRequestNotesUsecase = new ProveListInitUseCase(); } mUseCaseHandler.execute(mRequestNotesUsecase, new RequestNotesUsecase.RequestValues(type), new UseCase.UseCaseCallback () { @Override public void onSuccess(RequestNotesUsecase.ResponseValue response) { //viewModel 的可觀察數(shù)據(jù)發(fā)生變化后,databinding 會自動更新 UI 展示。 mListObservable.clear(); mListObservable.addAll(response.getNotes()); } @Override public void onError() { } }); } ... }
但 Clean 架構(gòu)也有不足:粒度太細 。一個 Usecase 受限于請求參數(shù),因而只能處理一類請求。View 請求的數(shù)據(jù)包含幾種類型,就至少需要準(zhǔn)備幾個 Usecase。Usecase 是依據(jù)當(dāng)前 View 對數(shù)據(jù)的需求量身定制的,因此 Usecase 的復(fù)用率極低,項目會因而急劇的增加類和重復(fù)代碼。
public class RequestNotesUseCase extends UseCaseAAC 架構(gòu)的特點{ private DataManager mDataManager; @Override protected void executeUseCase(final RequestValues values) { List noteBeans = mDataManager.getNotes(); ... getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans)); } //每新建一個 usecase 類,都需要手動為其配置 請求參數(shù)列表 和 響應(yīng)參數(shù)列表。 public static final class RequestValues implements UseCase.RequestValues { private String type; public String getType() { return type; } public void setType(String type) { this.type = type; } } public static final class ResponseValue implements UseCase.ResponseValue { public List mBeans; public ResponseValue(List beans) { mBeans = beans; } } }
AAC 也是數(shù)據(jù)驅(qū)動編程。只不過它不依賴于 MVVM 特性,而是直接在 View 中寫個觀察者回調(diào),以接收結(jié)果數(shù)據(jù)并處理 UI 邏輯。
public class NoteListFragment extends BaseFragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); viewModel.getNote().observe(this, new Observer() { @Override public void onChanged(@Nullable NoteBean bean) { //update UI } }); } ... }
你完全可以將其理解為 B/S 架構(gòu):從 Web 前端向 Web 后端發(fā)送了數(shù)據(jù)請求,后端在處理完畢后響應(yīng)結(jié)果數(shù)據(jù)給前端,前端再依據(jù)需求處理 UI 邏輯。等于說, AAC 將業(yè)務(wù)完全壓到了 Model 層。
ViaBus 架構(gòu)的由來及特點上一輪重構(gòu)項目在用 Clean 架構(gòu),為此我決定跳過 AAC,基于對移動端數(shù)據(jù)交互的理解,編寫“消息驅(qū)動編程”架構(gòu)。
由于借助總線來代理數(shù)據(jù)的請求和響應(yīng),因此取名 ViaBus。
不同于以往的架構(gòu),ViaBus 明確界定了什么是 UI,什么是業(yè)務(wù)。
UI 的作用是視覺交互,為此 UI 的職責(zé)范圍是請求數(shù)據(jù)和處理 UI 邏輯 。業(yè)務(wù)的作用是供應(yīng)數(shù)據(jù),因此 業(yè)務(wù)的職責(zé)范圍是接收請求、處理數(shù)據(jù)、返回結(jié)果數(shù)據(jù) 。
UI 不需要知道數(shù)據(jù)是怎么來的、通過誰來的,它只需向 bus 發(fā)送一個請求,如果有業(yè)務(wù)注冊了該類 “請求處理者”,那么自然有人來處理。業(yè)務(wù)也無需知道 UI 在拿到數(shù)據(jù)后會怎么用,它只需向 bus 回傳結(jié)果,如果有 UI 注冊了“觀察響應(yīng)者”,那么自然有人接收,并依據(jù)響應(yīng)碼行事。
這樣,在靜態(tài) bus 的加持下,UI 和業(yè)務(wù)是完全解耦的,從根本上解決了相互牽連的問題。此外,不同于上述架構(gòu)的每個 View 都要對應(yīng)一個 Presenter 或 ViewModel,在 ViaBus 中,一個模塊中的 UI 可以共享多個“業(yè)務(wù)處理者”實例,使 代碼的復(fù)用率提升到100%。
閱讀更多APP瘦身這一篇就夠了
一招教你打造一個滑動置頂?shù)囊曈X特效
Android組件化demo實現(xiàn)以及遇坑分享
(Android)面試題級答案(精選版)
歡迎關(guān)注我微信公眾號:終端研發(fā)部 ,如果您有什么問題可以一塊學(xué)習(xí)和交流
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/11933.html
閱讀 2626·2021-11-23 09:51
閱讀 860·2021-09-24 10:37
閱讀 3611·2021-09-02 15:15
閱讀 1961·2019-08-30 13:03
閱讀 1881·2019-08-29 15:41
閱讀 2624·2019-08-29 14:12
閱讀 1423·2019-08-29 11:19
閱讀 3300·2019-08-26 13:39