Android:四大架構的優缺點,你真的瞭解嗎?

聲明轉載於作者:KunMinX
原文鏈接:https://www.jianshu.com/p/9ef...

前言

前不久剛結束對 20 模塊項目的第 3 輪重構,一路見證 MVC、MVP、Clean 的優缺點並形成自己的體會。

近期在總結工作經驗的同時,開始寫博客。順便開源了我設計的 ViaBus 架構。

項目地址:
https://github.com/KunMinX/an...

項目常用架構比對

以下,對常見的 MVC、MVP、Clean、AAC 架構做個比對。

首先,一張表格展示各架構的類冗餘情況:

需求是,寫三個頁面,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個

MVC 架構的缺陷

  • View、Controller、Model 相互依賴,造成代碼耦合。
  • 難以分工,難以將 View、Controller、Model 分給不同的人寫。
  • 難以維護,沒有中間件接口做緩衝,難以替換底層的實現。
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 架構的特點與侷限

  • MVP 架構的特點是 面向接口編程。在 View、Presenter、Model 之間分別用 中間件接口 做銜接,當有新的底層實現時,能夠無縫替換。
  • 此外,MVP 的 View 和 Model 並不產生依賴,因此可以說是對 View 和 Model 做了代碼解耦。
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 嘛。

Clean 架構的特點和不足

爲解決 Presenter 職能邊界不明確 的問題,在 Clean 架構中,業務邏輯的職能被轉移到領域層,由 Usecase 專職管理。Presenter 則弱化爲 ViewModel ,作爲代理數據請求,和銜接數據回調的緩衝區。

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 架構的特點

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 層

ViaBus 架構的由來及特點

上一輪重構項目在用 Clean 架構,爲此我決定跳過 AAC,基於對移動端數據交互的理解,編寫“消息驅動編程”架構。

由於藉助總線來代理數據的請求和響應,因此取名 ViaBus。

不同於以往的架構,ViaBus 明確界定了什麼是 UI,什麼是業務。

UI 的作用是視覺交互,爲此 UI 的職責範圍是請求數據和處理 UI 邏輯 。業務的作用是供應數據,因此 業務的職責範圍是接收請求、處理數據、返回結果數據

UI 不需要知道數據是怎麼來的、通過誰來的,它只需向 bus 發送一個請求,如果有業務註冊了該類 “請求處理者”,那麼自然有人來處理。業務也無需知道 UI 在拿到數據後會怎麼用,它只需向 bus 回傳結果,如果有 UI 註冊了“觀察響應者”,那麼自然有人接收,並依據響應碼行事。

這樣,在靜態 bus 的加持下,UI 和業務是完全解耦的,從根本上解決了相互牽連的問題。此外,不同於上述架構的每個 View 都要對應一個 Presenter 或 ViewModel,在 ViaBus 中,一個模塊中的 UI 可以共享多個“業務處理者”實例,使 代碼的複用率提升到100%

閱讀更多

APP瘦身這一篇就夠了

一招教你打造一個滑動置頂的視覺特效

Android組件化demo實現以及遇坑分享

(Android)面試題級答案(精選版)

歡迎關注我微信公衆號:終端研發部 ,如果您有什麼問題可以一塊學習和交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章