本文主要介绍MVC、MVP和MVVM架构。至于MVI,后面会单独介绍。这些MVX的目的是为了将业务和视图分离,松耦合,作为Android程序员,大部分都不陌生。
一个App离不开Model和View这两个角色。Model决定了App的数据,View决定了数据如何展示给用户。大多数框架或组件基本上都是用来处理两者之间的交互的。.
因此,一个 App 的架构需要处理两个任务:
更新模型 - 如何处理视图操作?更新视图 - 如何将模型的数据呈现给视图?
基于此,Android(不包括MVI)上一般有以下三种常见架构:
MVC - Model-View-Controller:Actvity/Fragment等作为Controller层充当View角色,代码过于臃肿;同时,很容易直接在View层操作Model,导致View与Model层耦合,无法独立复用。有时看到一个 Activity 可以有数千甚至数万行代码是一场噩梦。
MVP - Model-View-Presenter:Presenter 和 View 层之间的通信是通过定义接口来实现的,这将 View 和 Model 层解耦。但是当业务场景比较复杂的时候,接口定义会越来越多,定义可能比较模糊。一旦接口改变,相应的实现也需要改变。
MVVM - Model-View-ViewModel:MVVM解决了MVP的问题,让ViewModel和View不再依赖接口通信,而是通过LiveData、RxJava、Flow等响应式开发方式进行通信。
我们可以看看这里对Model和View的理解:
View:视图,呈现给用户的界面,直接与用户交互的一层。
模型:模型通常应该包括数据和一些业务逻辑,即数据的结构定义,以及存储和检索。对于外部组件,模型通常代表提供给它们的数据。毕竟,他们不在乎数据从哪里来小程序定制案例,到哪里去,他们只关心自己。
MVC
该架构涉及三个角色:模型-视图-控制器。Controller是Model和View之间的桥梁,用来控制程序的流程。
记得在网上看了很多MVC的文章,但是好像有些文章给出的模型图不太一样,一时间有点疑惑。其实不同的是,MVC模型开发后有变种。MVC 的一个版本如下所示:
这个版本的一般交互流程是:
用户对View进行操作,例如产生点击事件。控制器接收事件并对它们做出反应。比如点击登录事件,会检查用户输入是否为空,如果为空则直接返回View提示用户;如果不为空,它将请求模型层。Model处理完毕后,需要将登录用户的数据通知给相关成员,也就是上图中的View层。View 收到后会做相关显示。
上图中,View层依赖于Model层,降低了View的复用性。对于解耦,出现以下版本:
这个版本的主要变化是 View 和 Model 不直接通信。View 通过 Controller 更新 Model 层的数据。Model层完成逻辑后,通知Controller层,Controller更新View。
MVC 架构总结
MVC 为视图和业务的分离提供了开创性的思路,解耦了 View 和 Model 层,提高了复用性。
但是,在Android的实际应用中,很容易出现一个新的角色——ViewController。比如Activity作为View和Controller,非常臃肿,耦合变得严重,不方便进行单元测试。
MVP
该架构涉及三个角色:Model-View-Presenter。关系图如下:
这张图和上面第二个版本的MVC结构非常相似。不同的是Controller被Presenter层代替,职责类似,但实现方式不同。MVP通过接口进行通信,三层都有自己的接口来定义自己的行为和能力,可以减少耦合,提高可重用性,方便单元测试。
交互过程还是如下:用户操作View层,产生事件;Presenter 接收事件,响应它,并请求 Model 层;Model层处理它并通知给Presenter,然后Presenter通知View层。
通过登录场景举个栗子
1、先定义各层的接口,一起写一个场景的接口。
interface ILogin {
interface ILoginView {
fun loginLoading() // 登陆中
fun loginResult(result: Boolean) // 登陆结果
fun isAvailable(): Boolean // IView 是否可用
}
interface ILoginPresenter {
fun attachView(view: ILoginView) // attach View
fun detachView() // detach View, 防止内存泄漏
fun isViewAvailable(): Boolean
fun login()
}
interface ILoginModel {
fun login(listener: OnLoginListener)
}
interface OnLoginListener {
fun result(result: Boolean)
}
}
2、查看层实现。
class MVPLoginActivity : AppCompatActivity(), ILogin.ILoginView {
private val loginPresenter = LoginPresenter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(Button(this).apply {
text = "登录"
setOnClickListener {
loginPresenter.login()
}
})
loginPresenter.attachView(this)
}
override fun loginLoading() {
Toast.makeText(this, "Login...", Toast.LENGTH_SHORT).show()
}
override fun loginResult(result: Boolean) {
Toast.makeText(this, "Login result: $result", Toast.LENGTH_SHORT).show()
}
override fun isAvailable() = !isDestroyed && !isFinishing
override fun onDestroy() {
super.onDestroy()
loginPresenter.detachView()
}
}
3、Presenter 层实现。
class LoginPresenter : ILogin.ILoginPresenter, ILogin.OnLoginListener {
private val loginModel: ILogin.ILoginModel = LoginModel()
private var loginView: ILogin.ILoginView? = null
override fun attachView(view: ILogin.ILoginView) {
loginView = view
}
override fun detachView() {
loginView = null
}
override fun isViewAvailable(): Boolean = loginView?.isAvailable() ?: false
override fun login() {
loginView?.loginLoading()
loginModel.login(this)
}
override fun result(result: Boolean) {
if (isViewAvailable()) {
loginView?.loginResult(result)
}
}
}
4、模型层实现。
class LoginModel : ILogin.ILoginModel {
override fun login(listener: ILogin.OnLoginListener) {
thread {
Thread.sleep(1000)
runOnUIThread {
// 返回登录结果
listener.result(Random.nextBoolean())
}
}
}
}
以上只是一个例子。在实际开发中,一些基本的重复逻辑当然会被抽取到Base类中。
MVP 架构总结
MVVM
MVVM 模式
该架构涉及三个角色:Model-View-ViewModel。关系图如下:
它看起来类似于 MVP。不同的是Presenter被ViewModel代替,ViewModel负责与Model层交互,以可观察对象的形式向View提供数据。ViewModel 与 View 层分离,即 ViewModel 不应该知道 View 正在与什么交互。
如前所述,Model层包括一些业务逻辑和业务数据模型,而ViewModel层就是View Model(视图模型),它包含了视图的呈现数据和逻辑。比如Model层的业务数据是1、2、3、4,当翻译到View层时,可能代表A、B、C、D。ViewModel除了这样做之外,还封装了视图,例如单击控件后的行为。还要注意,Jetpack 包中提供的 ViewModel 和 ViewModel 组件不是一回事。这里的 ViewModel 是一个概念,Jetpack 包提供了更方便的实现。
很多关于MVVM的文章例子都会用到DataBinding,但是没有DataBinding,你仍然可以使用MVVM架构小程序定制案例,比如LiveData、RxJava、Flow等。这些工具都是基于响应式开发的原理来替代基于接口的通信方式. 在实际开发中,我从未见过DataBinding的使用。另外,如果真的要使用DataBinding,尽量避免在xml中写代码逻辑。相反,您应该用变量替换它来表示属性并在 Kotlin 代码中分配它。
这里的响应式开发强调一种基于观察者模式的开发方式:View订阅ViewModel暴露的响应式接口,并在收到通知后执行相应的逻辑,而ViewModel不再持有任何形式的View引用,减少耦合 小程序定制企业 ,提高复用性。
另外,如果使用LiveData,ViewModel只将LiveData接口暴露给View层,是不允许直接在View层更新LiveData的,因为一旦View层有了直接更新LiveData的能力,就无法约束View层进行业务处理:
class LoginViewModel : ViewModel() {
private val _loginResult: MutableLiveData = MutableLiveData()
val loginResult: LiveData = _loginResult
}
关于流量小程序定制案例,我之前写过一篇关于掘金的学习笔记。如果您有兴趣,可以阅读:Kotlin Coroutines 中 Flow 的工作原理。
以登录结果为例,MVVM是基于LiveData的交互过程:首先,ViewModel中有一个LiveData属性来表示登录结果,它暴露的是LiveData而不是MutableLiveData,View层订阅了这个数据;View层点击登录后,调用VM的登录接口,VM请求Model层的登录能力;事件发生后Model通知VM 微信软件制作公司 ,VM更新MutableLiveData登录状态,View收到LiveData的变更通知,然后更新UI。
例子
1、Model层模拟登录并返回登录结果。
class LoginModel {
// 模拟登录
suspend fun login(): Boolean = withContext(Dispatchers.IO) {
delay(1000)
Random.nextBoolean()
}
}
2、ViewModel层暴露了登录方法,并提供LiveData数据来表示登录状态 开发一款app得多少钱 ,允许View层订阅。
class LoginViewModel : ViewModel(), CoroutineScope by MainScope() {
private val loginModel = LoginModel()
private val _loginResult: MutableLiveData = MutableLiveData()
val loginResult: LiveData = _loginResult
fun login() {
launch {
_loginResult.value = 0
val result = loginModel.login()
_loginResult.value = if (result) 1 else -1
}
}
// 模拟状态
fun loginProgressText(result: Int): String = when (result) {
0 -> "登录中"
1 -> "登录成功"
else -> "登录失败"
}
}
3、View 层处理点击事件并订阅登录状态。
class MVVMLoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by lazy {
ViewModelProvider(this).get(LoginViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(Button(this).apply {
text = "登录"
setOnClickListener {
viewModel.login()
}
})
// 监听登录状态
viewModel.loginResult.observe(this, {
Toast.makeText(
this,
"Login result: ${viewModel.loginProgressText(it)}",
Toast.LENGTH_SHORT
).show()
})
}
}
存储库
存储库模式的概念来自领域驱动设计。主要思想是通过抽象一个Repository层,业务(领域)层屏蔽不同数据源的访问细节,业务层(可能是ViewModel)不需要关注具体的数据访问细节。
Repository 在内部实现了对不同数据源(DataSource)的访问。典型的DataSource包括远程数据、Cache缓存、Database数据库等,可以用不同的Fetcher来实现。Repository 拥有多个 Fetcher 引用。
因此,可以将上例中的 LoginModel 替换为 LoginRepository 类。LoginRepository 没有暴露具体的数据访问方式 小程序开发一般多少价格 ,只是暴露了这个能力的接口。
最后
建筑不是一蹴而就的。希望有朝一日,我们能从自己写的代码中找到架构的成就感,而不是几票就跑掉。这类文章要时时更新,记录学习在建筑道路上的足迹 定制小程序公司 ,一一揭开建筑的奥秘。
在这里,我还分享了一个学习PDF+架构视频+面试文档+源码笔记,进阶架构技术进阶脑图,Android开发面试题目资料,以及老大收集整理的进阶进阶架构资料。
这些是我在业余时间会一遍又一遍地阅读的好材料。对近年来大厂访谈的高频知识点进行了详细讲解。相信它可以有效地帮助你掌握知识,理解原理,帮助你在日后得到一个好的答案。
当然,你也可以用它来检查差距,提高你的竞争力。
真心希望对大家有所帮助,安卓任重而道远,大家互相鼓励!
需要的可以私信我【进阶】获取