在抛弃 MVP-Clean 后,我自主设计并开源了 Viabus 架构
版权声明:本文为博主原创文章,转载请注明作者和链接。更多请继续关注 KunMinX
前言
前不久刚结束对 20 模块项目的第 3 轮重构,一路见证了 MVC、MVP、Clean 的优缺点并形成自己的体会。
近期在总结工作经验的同时,开始写博客。顺便开源了我设计的 ViaBus 架构。
项目地址:Github : KunMinX / android-viabus-architecture
⭐ 欢迎 star 和 fork ~
项目常用架构比对
以下,先对常见的 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() {@Overridepublic void run() {//view 中直接依赖 model。那么 view 须等 model 编写好才能开工。mNoteList = mDataManager.getNoteList();mHandler.sendMessage(REFRESH_LIST, mNoteList);}}).start();}private Handler mHandler = new Handler() {@Overridepublic 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;@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 直接干预了 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>() {@Overridepublic void onSuccess(RequestNotesUsecase.ResponseValue response) {//viewModel 的可观察数据发生变化后,databinding 会自动更新 UI 展示。mListObservable.clear();mListObservable.addAll(response.getNotes());}@Overridepublic void onError() {}});}...
}
但 Clean 架构也有不足:粒度太细 。一个 Usecase 受限于请求参数,因而只能处理一类请求。View 请求的数据包含几种类型,就至少需要准备几个 Usecase。Usecase 是依据当前 View 对数据的需求量身定制的,因此 Usecase 的复用率极低,项目会因而急剧的增加类和重复代码。
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));}//每新建一个 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 {@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 架构:从 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%。
ViaBus 现已在 Github 开源,欢迎 Star & Fork ~
更多访问
Github : KunMinX / android-viabus-architecture
1分钟掌握 ViaBus 架构的使用
ViaBus - 年轻人的第一款 Android 架构
在抛弃 MVP-Clean 后,我自主设计并开源了 Viabus 架构相关推荐
- Eclipse Maven clean后错误: 找不到或无法加载主类com.xxx.ShopApplication
如果com.xxx.ShopApplication原本可以启动但是在Maven中将模块clean后不能启动了,是因为系统找不到编译好的class文件,需要手动重新编译一下当前项目:Project-&g ...
- 用Kotlin语言开发玩安卓,基于基于Material Design+AndroidX + MVP + RxJava + Retrofit等优秀的开源框架开发,注释超详细,方便大家练手
WanAndroid 一位练习时长两年半的安卓练习生根据鸿神提供的WanAndroid开放Api来制作的产品级App,基本实现了所有的功能,使用Kotlin语言,基于Material Design+A ...
- 创业失败后,我决定开源所有产品代码
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 整理 | 李冬梅(AI前线) 近年来,程序员创业的话题屡屡被提及,那么,程序员究竟适不适合创业?又该如 ...
- 抛弃 CSS Hacks 后的浏览器兼容方案
一般情况下的浏览器兼容需要考虑 IE6/7/8 三种 IE 版本,当然在 IE9 开始逐步推向市场后,又会有更多的衍生版本.所以我目前只考虑 IE7~9 版本的兼容情况.涉及到的条件注释代码如下: & ...
- clean后class文件全部丢失_大数据专家,详解HadoopMapReduce处理海量小文件:压缩文件
前言 在HDFS上存储文件,大量的小文件是非常消耗NameNode内存的,因为每个文件都会分配一个文件描述符,NameNode需要在启动的时候加载全部文件的描述信息,所以文件越多,对NameNode来 ...
- idea redis 插件_Redis客户端RDM收费后,还有哪些开源的替代品呢?
作 者: Java面试那些事儿 原文链接:https://mp.weixin.qq.com/s/nyFOu1EEgWJxFOIAEFlUSw Redis的可视化客户端除了Redis Desktop M ...
- 北电的产品卖给Ciena后被拿去开源
北电的城域以太网事业部MEN卖给Ciena后,DRAC产品被Ciena开源了. 叫Open DRAC: http://www.opendrac.org/ DRAC意思是动态资源分配控制器,它的基本理念 ...
- 横扫六大权威榜单后,达摩院开源深度语言模型体系 AliceMind
整理 | AI 科技大本营(ID:rgznai100) 自然语言处理(NLP)被誉为 AI 皇冠上的明珠,传统 NLP 模型制作复杂,耗时耗力,且用途单一,难以复用.预训练语言模型是 NLP 领域的研 ...
- WebMessenger完善后将作为一个开源项目
先说说版权问题,现在WebMessenger客户端用的是微软的Web Msn Messenger的客户端,服务器端是我根据她的客户端模拟的一个,仅作学习用,不会传播和提供下载.因此目前的版本不方便开源 ...
最新文章
- Linux服务器程序编程的几个坎
- springcloud 与springboot的依赖关系以及版本的选择
- Remoting-1
- [css] 在sass中可以执行布尔运算吗?
- [转]VS 2003 常用快捷键
- 关于创建 LINQ to SQL 类时无法转换复数的问题(zhuan)
- 基于Spring Security的认证授权_认证原理_授权流程_Spring Security OAuth2.0认证授权---springcloud工作笔记126
- githua 账号合并_如何合并他人的分支 github
- 《3D数学基础》1.9 向量空间
- 苹果Mac 下 Parallels Desktop “无法连接到 Parallels 服务”的解决方法
- note同步不及时 one_续航800仅卖23万,比亚迪唐DM为啥却卖不过理想ONE?
- 数学建模真题训练——2003B题 CUMCM 露天矿生产的车辆安排(原创)
- centos pptp client 配置
- mysql ndb 安装_mysql NDB的安装配置使用示例
- 快速搭建多用户共享桌面云环境 云教室环境搭建
- Jieba分词的准确率提升:使用paddle模式进行分词(使用百度飞桨深度学习模型进行分词)
- 红帽 Linux 考试 要求
- 使用Kali上的Metasploit获取ssh登录到靶机权限
- 超硬核的Java工程师分享,什么是Java?为什么我要做Java,我是如何学习Java的?
- LuaBind --最强大的Lua C++ Bind