在抛弃 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() { @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%。
ViaBus 现已在 Github 开源,欢迎 Star & Fork ~
更多访问
Github : KunMinX / android-viabus-architecture
1分钟掌握 ViaBus 架构的使用
ViaBus - 年轻人的第一款 Android 架构
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Sublime Text插件的离线安装-使用htmlprettify美化您的HTML代码
Sublime Text是广大程序员喜欢的文本编辑器,Jerry觉得它最强大之处在于开放的架构,有丰富的插件为其提供各种各样额外的功能。 作为前端开发人员,经常需要从网上复制粘贴一些代码然后做修改,因此一个常见的需求就是对拷贝进Sublime Text的代码进行格式化,使其缩进,换行等符合规范。 为此,我在网上搜索到了一个非常有用的Sublime Text插件,专门用于美化HTML代码,名叫htmlprettify。 当我使用Sublime Text内置的包管理工具的Package Control: Install Package时: 遇到错误消息:Package Control: There are no packages available for installation. 因此在线安装插件这条路走不通了,然而感谢Sublime Text开放的架构,我还可以像Eclipse安装插件一样,走另一条离线安装之路。 到github这个repository里把HTMLPrettify的源代码下载到本地。 https://github.com/victorporof/Sublime-HTM...
- 下一篇
英特尔QSV技术在FFmpeg中的实现与使用
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vn9PLgZvnPs1522s82g/article/details/82892209 本文来自英特尔资深软件工程师张华在LiveVideoStackCon 2018讲师热身分享,并由LiveVideoStack整理而成。在分享中张华介绍了英特尔GPU硬件架构,并详细解析了英特尔QSV技术在FFmpeg中的具体实现与使用。 文 / 张华 整理 / LiveVideoStack 直播回放: https://www.baijiayun.com/web/playback/index?classid=18091958472800&session_id=201809200&token=PLFiH_sX1NNt681rrJ0J_ZTHDO9zanYEZBBB3Q06X5q9UJKvNPUPBpuOZ7Qxt3OtBkXP5cY2MAsKp0fXMnVKLQ 大家好,今天我与大家分享的是英特尔GPU架构以及Quick Sync Video技术在FFmepge中的实现与使用。 1、处理器...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Mario游戏-低调大师作品
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2更换Tomcat为Jetty,小型站点的福音