写的Android代码不能复用?从MVC开始重新学习
Posted QXXXD
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了写的Android代码不能复用?从MVC开始重新学习相关的知识,希望对你有一定的参考价值。
前言
android项目架构发展了这么多年,MVC,MVP,MVVM,现在又出现了MVI。架构层出不穷,可我一致感觉自己用的不太对。
MVP写的代码好像…和MVC写的差不多。用MVVM写的代码,emm 和之前的好像也差不多
我发现自己有一个能力,网上所有号称可扩展,可复用,可维护,灵活易测试的架构。到了我手里都变得不可扩展,不可复用,不可维护,不灵活,没测试过。 我一点都没有在含沙射影,只是觉得自己很牛逼 🐶
本文大概脉络从MVC的概念开始重新学习,讨论Android习惯写法的问题,Android MVC是如何演化到MVP的,讨论MVVM的特点,最终提出一种未实践过的理想写法
MVC概念讨论
MVC模式最早由Trygve Reenskaug在1978年提出,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件架构。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。
使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式
MVC把程序分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
模型(Model)表示业务规则 用于封装与程序的业务逻辑相关的数据以及对数据的处理方法。 模型有对数据直接访问的权力,例如对数据库的访问。模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据
视图(View)用户看到并与之交互的界面在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操作的方式。
控制器(Controller)起到不同层面间的组织作用,用于控制应用程序的流程。控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据
上述内容摘自维基百科和百度百科
概念辨析:
- 使用MVC的目的 或 期望目标
- 一大段程序的实现分离,职责清晰,互不影响
- 代码复用
- 模型表示业务规则。 注意用词是业务规则,这与我们日常对MVC的定义有些出入。常见的说法是数据。
- 数据实际上也是一个很大的概念,一般来说在Android中 把JavaBean 和 Repository 归为Model
- Repository 仓储类,用于获取数据对上层暴露接口屏蔽数据来源,比如:暴露获取用户信息接口,上层组件不需要知道用户信息是从网络获取的 还是从缓存中获取,上层组件只需要拿到数据。
- 根据定义 Model 和 数据应该是两个部分,再次回顾一下模型的定义
- 用于封装与程序的业务逻辑相关的数据以及对数据的处理方法
- Model 有对数据直接访问的权力,例如对数据库的访问
- 根据定义可以看出,Model 获取数据,Model 根据业务逻辑二次处理封装数据。这个描述符合业务规则的概念,逻辑不是 Controller 处理的么?怎么变成Model 处理了,这点也需要注意,到Controller 再谈
- Model 是中立的,可以被多个View使用。 这表明了Model中的代码一定是不与任何UI组件相关。
- 控制器,一言蔽之组织作用。
- Controller 本身不负责任何逻辑,它不展示数据,不处理数据,不产生数据,它是大自然的搬运工,只是串联View 和 Model。
- 再次与常见概念有很大的偏差,Controller 不负责逻辑处理,它的工作很少,组织代码而已
- 视图
- View展示给用户看的界面,有两部分工作
- 接收Controller 传递来的数据展示给用户
- 用户交互产生的数据传递给Controller
总结:
- Model的工作最重,获取数据,处理业务逻辑。
- Controller 很轻 负责串联Model 和 View
- View 很轻展示数据与用户交互
- 如果三层架构的形式划分
- 数据层:单独一部分
- 业务层:Model
- 展示层:Controller ,View
Android 与 MVC
Android天生就支持MVC
- Model:开发人员自定义,用户数据获取,业务逻辑
- View:XML文件,JavaView
- Controller:Activity,组织代码持有Model 和 View,处理数据展示
MVC工作流程图如下
回顾使用MVC的目的
- 实现分离
- 代码复用
实现分离经过上述讨论可以知道MVC很好的达成了目标。
现在考虑一下代码复用的问题
- Model,可以复用。如果有一个项目两个版本的开发经验的同学应该有体会。网络请求,缓存,数据处理等UI无关的业务逻辑是完全可以复用的。
- View,可以复用。简单的例子:自定义圆形头像,代码实现之后任意一个位置都可以使用,不用重复自定义过程
- Controller,很遗憾,不可复用 。
至少Android中的 Controller 不可复用,因为Controller 是 Activity。Model对象可以任意位置使用,View 也可以任意位置使用。但是Activity不行,它是系统创建的,开发人员无法管理Activity对象。想要复用Activity代码,一般情况下通过继承解决,但是不可能因为复用一段业务逻辑就定义一个抽象Activity。
因此B页面想要复用A页面中的一个加载网络头像功能流程如下:
- Model:提供网络头像地址,可复用直接new对象
- View:加载网络头像,可复用 在xml声明
- Controller:持有Model,View,绑定数据,不可复用 需要从A页面复制组织代码到B页面
到这里原有的AndroidMVC模型有问题,Activity 既是Controller 又是逻辑的调用者,四不像。
看代码
/** 模拟 Model, View, Controller */
var M = , V = , C = ;
/** Model 负责存放资料 */
M.data = "hello world";
/** View 负责将资料输出给用户 */
V.render = (M) => alert(M.data);
/** Controller 作为连接 M 和 V 的桥梁 */
C.handleOnload = () => V.render(M);
/** 在网页读取的时候呼叫 Controller */
window.onload = C.handleOnload;
JS 模拟的MVC案例,来自维基百科,结合注释很容易理解。除了Model,View,Controller,还有特别重要的一行,MVC的调用! window.onload = C.handleOnload;
在网页读取的时候调用 Controller
意义辨析:
- 先有一个需求,加载网络头像
- 不使用MVC,所有代码写在一起,代码层次不明显,难维护,无法复用
- 使用MVC后,代码分为三部分各司其职互不干扰,三部分代码共同解决需求—加载网络头像。
- 当页面A有展示网络头像的需求。页面A在加载时创建Controller对象,调用加载头像的方法。MVC组件自行工作,页面A对接Controller,Controller组织Model,View实现功能。
- 按照这种逻辑,页面B有展示网络头像的需求,同样创建Controller对象调用方法即可。实现代码复用
通过对比可以看出Android的MVC模型确实有点毛病,
window.onload 与 activity.onCreate 意义一致,代表页面的加载。
不同点在于JS案例中页面 和 Controller 是两个东西。 Android案例中 页面 和 Controller 是一个东西。
Android的MVC是残缺的MVC,M和V很明确,C模糊不清 或者没有C。所以Android中无法复用很彻底的复用代码。
MVC进化MVP
经过上面的讨论,发现Android中的MVC是残缺不全的,缺少C层。那就造个C层,创造新事物,起新名字,非常自然的MVP诞生了。
在Android中MVP才真正有点MVC的样子。
- Model:无变化
- View:Activity+XML
- Controller:Presenter
MVP把Activity 和 XML 一起当作View,View主要有两点工作
- 接收Controller传递的数据,展示给用户
- 接收用户交互产生的数据,传递给Controller
这样一来Activity承担的工作就很少了。
Presenter 才是真正 Controller。 持有Model,View,绑定数据,负责UI展示逻辑
Model 一旦确定万年不变
在编写Model 和 Presenter的时候,会有有点难以取舍,不知道什么样的代码放在Model,什么样的代码放在President。一个非常简单的判断方式,任何与UI发生关系的代码都不属于Model。
举个例子,加载用户网络头像
- Model:提供网络头像地址
- View:加载网络头像
- Controller:持有Model,View,绑定数据
如下伪代码所示,Activity 和 Presenter 非常轻,所有的数据逻辑细节都在Model中。这符合MVC的概念,M重,VC轻
MVP如果使用得当足以应付绝大部分开发工作,已经很完美了
class TestActivity : AppCompatActivity()
private val imageView:ImageView
private val presenter:UserPresenter
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
presenter = UserPresenter(this)
presenter.getHeadPortrait()
fun loadHeadPortrait(url:String)
imageView.loadUrl(url)
class UserPresenter(private val view:TestActivity)
private val userModel = UserModel()
fun getHeadPortrait()
userModel.getHeadPortrait(userId,object:Callback
fun onSuccess(url:String)
view.loadHeadPortrait(url)
)
MVP进化MVVM
首先需要明确的一点不是使用了 jetpack中的ViewModel组件就叫做MVVM。MVVM标志性特点是数据绑定。
除此之外MVVM与MVP并没有区别
那么什么叫做数据绑定呢?
需要用Vue举例,如下代码展示了WebView加载网络页面。
这个需求在Android中实现:
- 在xml中声明webView
- Activity 通过findViewById() 获取webView对象
- 接收其他页面传递参数
- webview.setUrl(参数)
在Vue中
- webview 绑定变量link
- 变量link的值被改变,webView自动刷新 开始工作
数据绑定,数据和UI组件直接绑定,当数据改变后UI组件自动跟着变化,不需要findViewById,setValue这种操作。
Android中findViewById()的开发方式,对应web开发的DOM操作。
Android中实现数据绑定需要使用 jetpack中的databinding组件,databinding只是一个三方库 没办法和vue对标。新出的compose 才可以和 vue对标,声明式UI,数据绑定。
<!-- View,声明了一个webView组件,绑定变量 link -->
<template>
<view>
<web-view v-bind:src="link"></web-view>
</view>
</template>
<script>
export default
//数据 定义了变量 link 默认空
data()
return
link: ""
;
,
//页面加载回调,接收页面传值,link变量赋值
onLoad(options)
let params = JSON.parse(options.params);
let title = params.title;
this.link = params.link;
uni.setNavigationBarTitle(
title: title
)
</script>
<style lang="less">
</style>
现有写法仍然无法复用
在Android中MVP是完美的MVC,MVVM进一步优化添加了数据绑定,代码分离已经做的很好了,但是仍然无法做到上面展示过的 维基百科JS MVC案例中在任意页面加载Controller的效果
/** 模拟 Model, View, Controller */
var M = , V = , C = ;
/** Model 负责存放资料 */
M.data = "hello world";
/** View 负责将资料输出给用户 */
V.render = (M) => alert(M.data);
/** Controller 作为连接 M 和 V 的桥梁 */
C.handleOnload = () => V.render(M);
/** 在网页读取的时候呼叫 Controller */
window.onload = C.handleOnload;
原因在于在Activity,开发人员无法管理Activity对象,只要Controller 或 View 与Activity扯上关系那肯定是无法复用的。
习惯写法MVC层次划分的粒度太大了,什么意思呢?
假设如图所示的首页需求:轮播图,功能入口,推荐列表。三个需求的数据全部从接口获取
为了实现功能创建的类 以及大概作用如下图
我个人长久以来都是这种写法,以页面为单位,以Activity为单位,造成的结果就是Model 和 Presenter 只使用 对应的页面。如果其他页面也要用到轮播图组件,Model 可能会复用,但是Controller (Presenter )和 View(Activity) 肯定是要重建的,无法复用。
原因在于以Activity为主的粒度太大了,以此构建的MVC不是MVC三个组件合力完成一个功能,而是把页面整体代码划分为三层,只有代码分离没有复用。
理想的写法
每一个activity,每一个页面看作应用场景。每一个场景下包含许多功能组件,每个功能模块由MVC实现。完成一个页面的功能就好像搭积木一样,按需加载加载功能组件,组件内部自动运行加载数据。
上图只是一个美好的设想并没有实践过。在实际开发中肯定会面对很多细节问题,但是以前的习惯写法也并非不好,毕竟平稳运行那么多年,但是当页面内容相当复杂的时候 也许可以利用上图的写法重构一下。
以上是关于写的Android代码不能复用?从MVC开始重新学习的主要内容,如果未能解决你的问题,请参考以下文章