在抛弃 MVP-Clean 后,我自主设计并开源了 Viabus 架构
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明作者和鏈接。更多請(qǐng)繼續(xù)關(guān)注 KunMinX
前言
前不久剛結(jié)束對(duì) 20 模塊項(xiàng)目的第 3 輪重構(gòu),一路見(jiàn)證了 MVC、MVP、Clean 的優(yōu)缺點(diǎn)并形成自己的體會(huì)。
近期在總結(jié)工作經(jīng)驗(yàn)的同時(shí),開(kāi)始寫(xiě)博客。順便開(kāi)源了我設(shè)計(jì)的 ViaBus 架構(gòu)。
項(xiàng)目地址:Github : KunMinX / android-viabus-architecture
? 歡迎 star 和 fork ~
項(xiàng)目常用架構(gòu)比對(duì)
以下,先對(duì)常見(jiàn)的 MVC、MVP、Clean、AAC 架構(gòu)做個(gè)比對(duì)。
首先,一張表格展示各架構(gòu)的類冗余情況:
需求是,寫(xiě)三個(gè)頁(yè)面,ListFragment、DetailFragment、PreviewFragment,每個(gè)頁(yè)面至少用到 3個(gè) Note 業(yè)務(wù)、3個(gè) User 業(yè)務(wù)。問(wèn):上述架構(gòu)分別需編寫(xiě)多少類?
| MVC | Fragment:3個(gè),Controller:3個(gè),Model:2個(gè) | 8個(gè) |
| MVP | Fragment:3個(gè),Presenter:3個(gè),Model:3個(gè),Contract:1個(gè) | 10個(gè) |
| Clean | Fragment:3個(gè),ViewModel:3個(gè),Usecase:18個(gè),Model:3個(gè) | 27個(gè) |
| AAC | Fragment:3個(gè),ViewModel:3個(gè),Model:3個(gè) | 9個(gè) |
MVC 架構(gòu)的缺陷
- View、Controller、Model 相互依賴,造成代碼耦合。
- 難以分工,難以將 View、Controller、Model 分給不同的人寫(xiě)。
- 難以維護(hù),沒(méi)有中間件接口做緩沖,難以替換底層的實(shí)現(xiàn)。
MVP 架構(gòu)的特點(diǎn)與局限
- MVP 架構(gòu)的特點(diǎn)是 面向接口編程。在 View、Presenter、Model 之間分別用 中間件接口 做銜接,當(dāng)有新的底層實(shí)現(xiàn)時(shí),能夠無(wú)縫替換。
- 此外,MVP 的 View 和 Model 并不產(chǎn)生依賴,因此可以說(shuō)是對(duì) View 和 Model 做了代碼解耦。
但 MVP 架構(gòu)有其局限性。按我的理解,MVP 設(shè)計(jì)的初衷是, “讓天下沒(méi)有難替換的 View 和 Model” 。該初衷背后所基于的假設(shè)是,“上層邏輯穩(wěn)定,但底層實(shí)現(xiàn)更替頻繁” 。在這個(gè)假設(shè)的引導(dǎo)下,使得三者中, 只有 Presenter 具備獨(dú)立意志和決定權(quán),掌管著 UI 邏輯和業(yè)務(wù)邏輯,而 View 和 Model 只是外接的工具。
public class NoteListPresenter implements NoteListContract.INoteListPresenter {private NoteListContract.INoteListModel mDataManager;private NoteListContract.INoteListView mView;@Overridepublic void requestNotes(String type) {Observable.create(new ObservableOnSubscribe<List<NoteBean>>() {@Overridepublic 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>>() {@Overridepublic void accept(List<NoteBean> beans) throws Exception {//presenter 直接干預(yù)了 UI 在拿到數(shù)據(jù)后做什么,使得邏輯上沒(méi)有發(fā)生解耦。//正常來(lái)說(shuō),解耦意味著,presenter 的職能邊界僅限返回結(jié)果數(shù)據(jù),//由 UI 來(lái)依據(jù)響應(yīng)碼處理 UI 邏輯。mView.refreshList(beans);}});}... }然而,這樣的假設(shè)多數(shù)時(shí)候并不實(shí)際。可視化需求是變化多端的,在牽涉到視覺(jué)交互時(shí),必然涉及 UI 邏輯的修改,也就是說(shuō),View 和 Presenter 的相互牽連,使得 UI 的改動(dòng)需要 View 和 Presenter 編寫(xiě)者配合著完成,增加溝通協(xié)作成本。
長(zhǎng)久來(lái)看,二者都難以成長(zhǎng)。Presenter 編寫(xiě)者容易被各種非本職工作拖累,View 的編寫(xiě)者不會(huì)嘗試獨(dú)立自主,例如通過(guò)多態(tài)等模式將 UI 封裝成可適應(yīng)性的組件,反正 ... 有 Presenter 來(lái)各種 if else 嘛。
Clean 架構(gòu)的特點(diǎn)和不足
為解決 Presenter 職能邊界不明確 的問(wèn)題,在 Clean 架構(gòu)中,業(yè)務(wù)邏輯的職能被轉(zhuǎn)移到領(lǐng)域?qū)?#xff0c;由 Usecase 專職管理。Presenter 則弱化為 ViewModel ,作為代理數(shù)據(jù)請(qǐng)求,和銜接數(shù)據(jù)回調(diào)的緩沖區(qū)。
Clean 架構(gòu)的特點(diǎn)是 單向依賴、數(shù)據(jù)驅(qū)動(dòng)編程。 View -> ViewModel -> Usecase -> Model 。
View 對(duì) ViewModel 的單向依賴,是通過(guò) databinding 特性實(shí)現(xiàn)的。ViewModel 只負(fù)責(zé)代理數(shù)據(jù)請(qǐng)求,在 Usecase 處理完業(yè)務(wù)返回結(jié)果數(shù)據(jù)時(shí),結(jié)果數(shù)據(jù)被賦值給可觀察的 databinding 數(shù)據(jù),而 View 則依據(jù)數(shù)據(jù)的變化而變化。
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>() {@Overridepublic void onSuccess(RequestNotesUsecase.ResponseValue response) {//viewModel 的可觀察數(shù)據(jù)發(fā)生變化后,databinding 會(huì)自動(dòng)更新 UI 展示。mListObservable.clear();mListObservable.addAll(response.getNotes());}@Overridepublic void onError() {}});}... }但 Clean 架構(gòu)也有不足:粒度太細(xì) 。一個(gè) Usecase 受限于請(qǐng)求參數(shù),因而只能處理一類請(qǐng)求。View 請(qǐng)求的數(shù)據(jù)包含幾種類型,就至少需要準(zhǔn)備幾個(gè) Usecase。Usecase 是依據(jù)當(dāng)前 View 對(duì)數(shù)據(jù)的需求量身定制的,因此 Usecase 的復(fù)用率極低,項(xiàng)目會(huì)因而急劇的增加類和重復(fù)代碼。
public class RequestNotesUseCase extends UseCase<RequestNotesUseCase.RequestValues, RequestNotesUseCase.ResponseValue> {private DataManager mDataManager;@Overrideprotected void executeUseCase(final RequestValues values) {List<NoteBean> noteBeans = mDataManager.getNotes();...getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans));}//每新建一個(gè) usecase 類,都需要手動(dòng)為其配置 請(qǐng)求參數(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<NoteBean> mBeans;public ResponseValue(List<NoteBean> beans) {mBeans = beans;}} }AAC 架構(gòu)的特點(diǎn)
AAC 也是數(shù)據(jù)驅(qū)動(dòng)編程。只不過(guò)它不依賴于 MVVM 特性,而是直接在 View 中寫(xiě)個(gè)觀察者回調(diào),以接收結(jié)果數(shù)據(jù)并處理 UI 邏輯。
public class NoteListFragment extends BaseFragment {@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);viewModel.getNote().observe(this, new Observer<NoteBean>() {@Overridepublic void onChanged(@Nullable NoteBean bean) {//update UI}});}... }你完全可以將其理解為 B/S 架構(gòu):從 Web 前端向 Web 后端發(fā)送了數(shù)據(jù)請(qǐng)求,后端在處理完畢后響應(yīng)結(jié)果數(shù)據(jù)給前端,前端再依據(jù)需求處理 UI 邏輯。等于說(shuō), AAC 將業(yè)務(wù)完全壓到了 Model 層。
ViaBus 架構(gòu)的由來(lái)及特點(diǎn)
上一輪重構(gòu)項(xiàng)目在用 Clean 架構(gòu),為此我決定跳過(guò) AAC,基于對(duì)移動(dòng)端數(shù)據(jù)交互的理解,編寫(xiě)“消息驅(qū)動(dòng)編程”架構(gòu)。
由于借助總線來(lái)代理數(shù)據(jù)的請(qǐng)求和響應(yīng),因此取名 ViaBus。
不同于以往的架構(gòu),ViaBus 明確界定了什么是 UI,什么是業(yè)務(wù)。
UI 的作用是視覺(jué)交互,為此 UI 的職責(zé)范圍是請(qǐng)求數(shù)據(jù)和處理 UI 邏輯 。業(yè)務(wù)的作用是供應(yīng)數(shù)據(jù),因此 業(yè)務(wù)的職責(zé)范圍是接收請(qǐng)求、處理數(shù)據(jù)、返回結(jié)果數(shù)據(jù) 。
UI 不需要知道數(shù)據(jù)是怎么來(lái)的、通過(guò)誰(shuí)來(lái)的,它只需向 bus 發(fā)送一個(gè)請(qǐng)求,如果有業(yè)務(wù)注冊(cè)了該類 “請(qǐng)求處理者”,那么自然有人來(lái)處理。業(yè)務(wù)也無(wú)需知道 UI 在拿到數(shù)據(jù)后會(huì)怎么用,它只需向 bus 回傳結(jié)果,如果有 UI 注冊(cè)了“觀察響應(yīng)者”,那么自然有人接收,并依據(jù)響應(yīng)碼行事。
這樣,在靜態(tài) bus 的加持下,UI 和業(yè)務(wù)是完全解耦的,從根本上解決了相互牽連的問(wèn)題。此外,不同于上述架構(gòu)的每個(gè) View 都要對(duì)應(yīng)一個(gè) Presenter 或 ViewModel,在 ViaBus 中,一個(gè)模塊中的 UI 可以共享多個(gè)“業(yè)務(wù)處理者”實(shí)例,使 代碼的復(fù)用率提升到100%。
ViaBus 現(xiàn)已在 Github 開(kāi)源,歡迎 Star & Fork ~
更多訪問(wèn)
Github : KunMinX / android-viabus-architecture
1分鐘掌握 ViaBus 架構(gòu)的使用
ViaBus - 年輕人的第一款 Android 架構(gòu)
總結(jié)
以上是生活随笔為你收集整理的在抛弃 MVP-Clean 后,我自主设计并开源了 Viabus 架构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Perl输出复杂数据结构:Data::D
- 下一篇: react使用引入svg的icon;sv