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)面试题级答案(精选版)

欢迎关注我微信公众号:终端研发部 ,若是您有什么问题能够一块学习和交流

相关文章
相关标签/搜索