聲明轉載於做者:KunMinX
原文連接:https://www.jianshu.com/p/9ef...前端
前不久剛結束對 20 模塊項目的第 3 輪重構,一路見證 MVC、MVP、Clean 的優缺點並造成本身的體會。android
近期在總結工做經驗的同時,開始寫博客。順便開源了我設計的 ViaBus 架構。git
項目地址:
https://github.com/KunMinX/an...
如下,對常見的 MVC、MVP、Clean、AAC 架構作個比對。github
首先,一張表格展現各架構的類冗餘狀況:面試
需求是,寫三個頁面,ListFragment、DetailFragment、PreviewFragment,每一個頁面至少用到 3個 Note 業務、3個 User 業務。問:上述架構分別需編寫多少類?編程
架構 | 涉及類 | 類總數 |
---|---|---|
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個 |
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: } } }; ... }
public class NoteListContract { interface INoteListView { void showDialog(String msg); void showTip(String tip); void refreshList(List<NoteBean> beans); } interface INoteListPresenter { void requestNotes(String type); void updateNotes(NoteBean... beans); void deleteNotes(NoteBean... beans); } interface INoteListModel { List<NoteBean> getNoteList(); int updateNote(NoteBean bean); int deleteNote(NoteBean bean); } }
但 MVP 架構有其侷限性。按個人理解,MVP 設計的初衷是, 「讓天下沒有難替換的 View 和 Model」 。該初衷背後所基於的假設是,「上層邏輯穩定,但底層實現更替頻繁」 。在這個假設的引導下,使得三者中, 只有 Presenter 具有獨立意志和決定權,掌管着 UI 邏輯和業務邏輯,而 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<List<NoteBean>>() { @Override public void subscribe(ObservableEmitter<List<NoteBean>> e) throws Exception { List<NoteBean> noteBeans = mDataManager.getNoteList(); e.onNext(noteBeans); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<List<NoteBean>>() { @Override public void accept(List<NoteBean> beans) throws Exception { //presenter 直接干預了 UI 在拿到數據後作什麼,使得邏輯上沒有發生解耦。 //正常來講,解耦意味着,presenter 的職能邊界僅限返回結果數據, //由 UI 來依據響應碼處理 UI 邏輯。 mView.refreshList(beans); } }); } ... }
然而,這樣的假設多數時候並不實際。可視化需求是變化無窮的,在牽涉到視覺交互時,必然涉及 UI 邏輯的修改,也就是說,View 和 Presenter 的相互牽連,使得 UI 的改動須要 View 和 Presenter 編寫者配合着完成,增長溝通協做成本。微信
長久來看,兩者都難以成長。Presenter 編寫者容易被各類非本職工做拖累,View 的編寫者不會嘗試獨立自主,例如經過多態等模式將 UI 封裝成可適應性的組件,反正 ... 有 Presenter 來各類 if else 嘛。架構
爲解決 Presenter 職能邊界不明確 的問題,在 Clean 架構中,業務邏輯的職能被轉移到領域層,由 Usecase 專職管理。Presenter 則弱化爲 ViewModel ,做爲代理數據請求,和銜接數據回調的緩衝區。ide
Clean 架構的特色是 單向依賴、數據驅動編程。 View -> ViewModel -> Usecase -> Model
。
View 對 ViewModel 的單向依賴,是經過 databinding 特性實現的。ViewModel 只負責代理數據請求,在 Usecase 處理完業務返回結果數據時,結果數據被賦值給可觀察的 databinding 數據,而 View 則依據數據的變化而變化。
public class NoteListViewModel { private ObservableList<NoteBean> mListObservable = new ObservableArrayList<>(); private void requestNotes(String type) { if (null == mRequestNotesUsecase) { mRequestNotesUsecase = new ProveListInitUseCase(); } mUseCaseHandler.execute(mRequestNotesUsecase, new RequestNotesUsecase.RequestValues(type), new UseCase.UseCaseCallback<RequestNotesUsecase.ResponseValue>() { @Override public void onSuccess(RequestNotesUsecase.ResponseValue response) { //viewModel 的可觀察數據發生變化後,databinding 會自動更新 UI 展現。 mListObservable.clear(); mListObservable.addAll(response.getNotes()); } @Override public void onError() { } }); } ... }
但 Clean 架構也有不足:粒度太細 。一個 Usecase 受限於請求參數,於是只能處理一類請求。View 請求的數據包含幾種類型,就至少須要準備幾個 Usecase。Usecase 是依據當前 View 對數據的需求量身定製的,所以 Usecase 的複用率極低,項目會於是急劇的增長類和重複代碼。
public class RequestNotesUseCase extends UseCase<RequestNotesUseCase.RequestValues, RequestNotesUseCase.ResponseValue> { private DataManager mDataManager; @Override protected void executeUseCase(final RequestValues values) { List<NoteBean> noteBeans = mDataManager.getNotes(); ... getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans)); } //每新建一個 usecase 類,都須要手動爲其配置 請求參數列表 和 響應參數列表。 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<NoteBean> mBeans; public ResponseValue(List<NoteBean> beans) { mBeans = beans; } } }
AAC 也是數據驅動編程。只不過它不依賴於 MVVM 特性,而是直接在 View 中寫個觀察者回調,以接收結果數據並處理 UI 邏輯。
public class NoteListFragment extends BaseFragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); viewModel.getNote().observe(this, new Observer<NoteBean>() { @Override public void onChanged(@Nullable NoteBean bean) { //update UI } }); } ... }
你徹底能夠將其理解爲 B/S 架構:從 Web 前端向 Web 後端發送了數據請求,後端在處理完畢後響應結果數據給前端,前端再依據需求處理 UI 邏輯。等於說, AAC 將業務徹底壓到了 Model 層。
上一輪重構項目在用 Clean 架構,爲此我決定跳過 AAC,基於對移動端數據交互的理解,編寫「消息驅動編程」架構。
因爲藉助總線來代理數據的請求和響應,所以取名 ViaBus。
不一樣於以往的架構,ViaBus 明確界定了什麼是 UI,什麼是業務。
UI 的做用是視覺交互,爲此 UI 的職責範圍是請求數據和處理 UI 邏輯 。業務的做用是供應數據,所以 業務的職責範圍是接收請求、處理數據、返回結果數據 。
UI 不須要知道數據是怎麼來的、經過誰來的,它只需向 bus 發送一個請求,若是有業務註冊了該類 「請求處理者」,那麼天然有人來處理。業務也無需知道 UI 在拿到數據後會怎麼用,它只需向 bus 回傳結果,若是有 UI 註冊了「觀察響應者」,那麼天然有人接收,並依據響應碼行事。
這樣,在靜態 bus 的加持下,UI 和業務是徹底解耦的,從根本上解決了相互牽連的問題。此外,不一樣於上述架構的每一個 View 都要對應一個 Presenter 或 ViewModel,在 ViaBus 中,一個模塊中的 UI 能夠共享多個「業務處理者」實例,使 代碼的複用率提高到100%。
歡迎關注我微信公衆號:終端研發部 ,若是您有什麼問題能夠一塊學習和交流