Android:四大架构的优缺点,你真的了解吗?
声明|转载于作者:KunMinX
原文链接:https://www.jianshu.com/p/9ef813d5c1af
前言
前不久刚结束对 20 模块项目的第 3 轮重构,一路见证 MVC、MVP、Clean 的优缺点并形成自己的体会。
近期在总结工作经验的同时,开始写博客。顺便开源了我设计的 ViaBus 架构。
项目常用架构比对
以下,对常见的 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%。
阅读更多
Android:四大架构的优缺点,你真的了解吗?的更多相关文章
- Android 四大组件 与 MVC 架构模式
作为一个刚从JAVA转过来的Android程序员总会思考android MVC是什么样的? 首先,我们必须得说Android追寻着MVC架构,那就得先说一下MVC是个啥东西! 总体而来说MVC不能说是 ...
- Android零基础入门第2节:Android 系统架构和应用组件那些事
原文:Android零基础入门第2节:Android 系统架构和应用组件那些事 继上一期浅谈了Android的前世今生,这一期一起来大致回顾一下Android 系统架构和应用组件. 一.Android ...
- Android四大基本组件介绍与生命周期
Android四大基本组件介绍与生命周期 Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器 ...
- android四大组件详解--摘
Android四大基本组件介绍与生命周期 Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器 ...
- Android四大组件详解
注:本文主要来自网易的一个博主的文章,经过阅读,总结,故留下文章在此 Android四大基本组件介绍与生命周期 Android四大基本组件分别是Activity,Service服务,Content P ...
- android sensor架构
Android Sensor 架构深入剖析 作者:倪键树,华清远见嵌入式学院讲师. 1.Android sensor架构 Android4.0系统内置对传感器的支持达13种,它们分别是:加速度传感器 ...
- 改造 Android 官方架构组件 ViewModel
前言 Android 官方架构组件在今年 5 月份 Google I/O 大会上被公布, 直到 11 月份一直都是测试版, 由于工作比较繁忙, 期间我只是看过类似的文章, 但没有在实际项目中使用过, ...
- 【转】Android四大基本组件介绍与生命周期
转自:http://www.cnblogs.com/bravestarrhu/archive/2012/05/02/2479461.html Android四大基本组件分别是Activity,Serv ...
- Android官方架构组件指南
此指南适用于那些曾经或现在进行Android应用的基础开发,并希望了解和学习编写Android程序的最佳实践和架构.通过学习来构建强大的生产级别的应用. 注意:此指南默认你对Android开发有比较深 ...
随机推荐
- 一张图教你弄清楚linux虚拟机的静态IP设置原理 VMnet8
- HTTP访问控制(CORS)
当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求. 比如,站点 http://domain-a.com 的某 HTML 页面通过 <img ...
- 第27月第10天 cmake
1.error: tool 'xcodebuild' requires Xcode的解决办法 sudo xcode-select --switch /Applications/Xcode.app/Co ...
- t-sql对被除数为0&除数小于被除数结果为0&除法保留2位小数的处理
SELECT round(CAST(12 AS FLOAT)/nullif(13,0),2,1) FROM TB
- A - Brackets POJ - 2955 (区间DP模板题)
题目链接:https://cn.vjudge.net/contest/276243#problem/A 题目大意:给你一个字符串,让你求出字符串的最长匹配子串. 具体思路:三个for循环暴力,对于一个 ...
- Python写一个京东抢券脚本
最近看到京东图书每天有优惠券发放,满200减100,诱惑还是蛮大的.反正自己抢不到,想着写个脚本试试. 几个关键步骤 获取优惠券的url 直接审查元素 获取cookie 通过本地代理,比如BurpSu ...
- 论文笔记:Batch Normalization
在神经网络的训练过程中,总会遇到一个很蛋疼的问题:梯度消失/爆炸.关于这个问题的根源,我在上一篇文章的读书笔记里也稍微提了一下.原因之一在于我们的输入数据(网络中任意层的输入)分布在激活函数收敛的区域 ...
- 1-OSI七层模型详解
1.网络协议种类 市面上存在4,5,7层协议. 1.1 国际标准化组织ISO发布的OSI 7层协议模型(即OSI开放式互联参考模型),是概念性模型. 1.2 TCP/IP是一种实践类的模型,已成为行业 ...
- Spring @Bean注解 (基于java的容器注解)
基于java的容器注解,意思就是使用Java代码以及一些注解,就可以取代spring 的 xml配置文件. 1-@Configuration & @Bean的配合 @Configuration ...
- SpringSecurityOAuth使用JWT Token实现SSO单点登录
⒈认证服务器 1.添加pom依赖 <dependency> <groupId>org.springframework.boot</groupId> <arti ...