写的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)起到不同层面间的组织作用,用于控制应用程序的流程。控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据

上述内容摘自维基百科和百度百科

概念辨析:

  1. 使用MVC的目的 或 期望目标
    1. 一大段程序的实现分离,职责清晰,互不影响
    2. 代码复用
  2. 模型表示业务规则。 注意用词是业务规则,这与我们日常对MVC的定义有些出入。常见的说法是数据
    1. 数据实际上也是一个很大的概念,一般来说在Android中 把JavaBean 和 Repository 归为Model
    2. Repository 仓储类,用于获取数据对上层暴露接口屏蔽数据来源,比如:暴露获取用户信息接口,上层组件不需要知道用户信息是从网络获取的 还是从缓存中获取,上层组件只需要拿到数据。
    3. 根据定义 Model 和 数据应该是两个部分,再次回顾一下模型的定义
      1. 用于封装与程序的业务逻辑相关的数据以及对数据的处理方法
      2. Model 有对数据直接访问的权力,例如对数据库的访问
    4. 根据定义可以看出,Model 获取数据,Model 根据业务逻辑二次处理封装数据。这个描述符合业务规则的概念,逻辑不是 Controller 处理的么?怎么变成Model 处理了,这点也需要注意,到Controller 再谈
    5. Model 是中立的,可以被多个View使用。 这表明了Model中的代码一定是不与任何UI组件相关。
  3. 控制器,一言蔽之组织作用。
    1. Controller 本身不负责任何逻辑,它不展示数据,不处理数据,不产生数据,它是大自然的搬运工,只是串联View 和 Model。
    2. 再次与常见概念有很大的偏差,Controller 不负责逻辑处理,它的工作很少,组织代码而已
  4. 视图
    1. View展示给用户看的界面,有两部分工作
    2. 接收Controller 传递来的数据展示给用户
    3. 用户交互产生的数据传递给Controller

总结:

  1. Model的工作最重,获取数据,处理业务逻辑。
  2. Controller 很轻 负责串联Model 和 View
  3. View 很轻展示数据与用户交互
  4. 如果三层架构的形式划分
    1. 数据层:单独一部分
    2. 业务层:Model
    3. 展示层:Controller ,View

Android 与 MVC

Android天生就支持MVC

  1. Model:开发人员自定义,用户数据获取,业务逻辑
  2. View:XML文件,JavaView
  3. Controller:Activity,组织代码持有Model 和 View,处理数据展示

MVC工作流程图如下

回顾使用MVC的目的

  1. 实现分离
  2. 代码复用

实现分离经过上述讨论可以知道MVC很好的达成了目标。

现在考虑一下代码复用的问题

  1. Model,可以复用。如果有一个项目两个版本的开发经验的同学应该有体会。网络请求,缓存,数据处理等UI无关的业务逻辑是完全可以复用的。
  2. View,可以复用。简单的例子:自定义圆形头像,代码实现之后任意一个位置都可以使用,不用重复自定义过程
  3. Controller,很遗憾,不可复用 。

至少Android中的 Controller 不可复用,因为Controller 是 Activity。Model对象可以任意位置使用,View 也可以任意位置使用。但是Activity不行,它是系统创建的,开发人员无法管理Activity对象。想要复用Activity代码,一般情况下通过继承解决,但是不可能因为复用一段业务逻辑就定义一个抽象Activity。

因此B页面想要复用A页面中的一个加载网络头像功能流程如下:

  1. Model:提供网络头像地址,可复用直接new对象
  2. View:加载网络头像,可复用 在xml声明
  3. 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

意义辨析:

  1. 先有一个需求,加载网络头像
  2. 不使用MVC,所有代码写在一起,代码层次不明显,难维护,无法复用
  3. 使用MVC后,代码分为三部分各司其职互不干扰,三部分代码共同解决需求—加载网络头像。
  4. 当页面A有展示网络头像的需求。页面A在加载时创建Controller对象,调用加载头像的方法。MVC组件自行工作,页面A对接Controller,Controller组织Model,View实现功能。
  5. 按照这种逻辑,页面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的样子。

  1. Model:无变化
  2. View:Activity+XML
  3. Controller:Presenter

MVP把Activity 和 XML 一起当作View,View主要有两点工作

  1. 接收Controller传递的数据,展示给用户
  2. 接收用户交互产生的数据,传递给Controller

这样一来Activity承担的工作就很少了。

Presenter 才是真正 Controller。 持有Model,View,绑定数据,负责UI展示逻辑

Model 一旦确定万年不变

在编写Model 和 Presenter的时候,会有有点难以取舍,不知道什么样的代码放在Model,什么样的代码放在President。一个非常简单的判断方式,任何与UI发生关系的代码都不属于Model。

举个例子,加载用户网络头像

  1. Model:提供网络头像地址
  2. View:加载网络头像
  3. 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中实现:

  1. 在xml中声明webView
  2. Activity 通过findViewById() 获取webView对象
  3. 接收其他页面传递参数
  4. webview.setUrl(参数)

在Vue中

  1. webview 绑定变量link
  2. 变量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开始重新学习的主要内容,如果未能解决你的问题,请参考以下文章

关于Android MVP模式的思考

框架模式 MVC 在Android中的使用

框架模式 MVC 在Android中的使用

[转]框架模式 MVC 在Android中的使用

今天给大家带来新手学Java常见的误区

今天给大家带来新手学Java常见的误区