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

聲明轉載於做者: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個

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 ,做爲代理數據請求,和銜接數據回調的緩衝區。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 架構的特色

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)面試題級答案(精選版)

歡迎關注我微信公衆號:終端研發部 ,若是您有什麼問題能夠一塊學習和交流

相關文章
相關標籤/搜索