Android MVC , MVP, MVVM 架构案例学习

23 篇文章 1 订阅
1 篇文章 0 订阅

作者:苍耳叔叔

前言

主要介绍一下 MVC, MVP 以及 MVVM 架构,至于 MVI 后面会单独介绍。这些 MVX 的目的都是为了将业务和视图分离,松耦合,作为 Android 程序猿,大多不陌生了。

一个 App 离不开 Model 和 View 这两个角色, Model 决定了 App 的数据,而 View 决定怎么向用户展示这些数据,大多框架或组件基本上都是用来处理这两者之间的交互关系的。

因此一个 App 的架构需要处理两个任务:

  1. 更新 Model —— 如何处理 View action?
  2. 更新 View —— 如何将 Model 的数据表现到 View 上?

基于此,在 Android 上一般有如下三种常用架构(本期不讲 MVI):

  • MVC —— Model-View-Controller: 作为 Controller 层的 Actvity/Fragment 等充当了 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: 视图,向用户呈现的界面,与用户直接交互的一层。
  • Model: Model 通常应包括数据和一些业务逻辑,即数据的结构定义,以及存储和获取等。而针对外部组件而言, Model 往往表示向其提供的数据,毕竟它们不关心数据是咋来的,咋走的,它们只关心它们自己。

MVC

该架构涉及三个角色: Model-View-Controller。其中 Controller 是 Model 与 View 之间的桥梁,用来控制程序的流程。

我记得曾经在网上看过不少 MVC 的文章,但是貌似有些文章里面给的模型图不太一样,一度有些费解,其实这些不一样的地方在于 MVC 模型经过发展存在着变体而已。一个版本的 MVC 是这样子的:

该版本一般的交互流程是:

  1. 用户操作 View, 比如说产生了一个点击事件。
  2. Controller 接收事件,对其作出反应。比如说是点击登录事件,它会校验用户输入是否为空,若为空则直接返回 View 让其提示用户;若不为空则请求 Model 层。
  3. 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、View 层实现

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、Model 层实现

class LoginModel : ILogin.ILoginModel {
    override fun login(listener: ILogin.OnLoginListener) {
        thread {
            Thread.sleep(1000)
            runOnUIThread {
                // 返回登录结果
                listener.result(Random.nextBoolean())
            }
        }
    }
}

以上只是一个示例,实际开发中当然会把一些基础的重复的逻辑抽成 Base 类

MVP 架构小结

  • MVP 模式清晰划分了各个层的职责,避免了 ViewController 的问题,降低了代码的臃肿程度。
  • 解除 View 与 Model 的耦合,通过接口来交互,提高了可复用性和扩展性,利于单元测试。
  • 但随着业务的复杂化,接口的定义越来越多,提高了项目的复杂度,对开发的设计能力要求也更高了。
  • Presenter 如果持有 Activity 等的引用,容易出现内存泄漏,生命周期不同步等问题。

MVVM

MVVM模式

该架构涉及三个角色: Model-View-ViewModel。关系图如下:

它跟 MVP 看起来也是比较类似的,不同之处在于 Presenter 换成了 ViewModel, ViewModel 负责与 Model 层交互,并且将数据以可观察对象的形式提供给 View, ViewModel 与 View 层分离,即 ViewModel 不应该知道与之交互的 View 是什么。

上面说过 Model 层里包括了一些业务逻辑和业务数据模型,而 ViewModel 层即是视图模型(Model of View),其内是视图的表示数据和逻辑。比如说 Model 层的业务数据是 1, 2, 3, 4, 而翻译到 View 层,则可能是表示 A, B, C, D 了。ViewModel 除了做这个事情外,还会封装视图的行为动作,如点击某个控件后的行为等。另外注意这里的 ViewModel 和 Jetpack 包里提供的 ViewModel 组件不是一个东西,这里的 ViewModel 是一个概念,而 Jetpack 包则提供了一个比较方便的实现方式。

很多讲 MVVM 的文章示例都会用 DataBinding, 然而没有 DataBinding 照样可以使用 MVVM 架构,比如说借用 LiveData, RxJava, Flow 等,这些工具都是基于响应式开发的原理,来替代基于接口的通信方式。实际开发中基本没看到过使用 DataBinding 的,另外如果真要使用 DataBinding 的话,尽量避免在 xml 里写代码逻辑,而应替换成变量来表示某个属性,在 Kotlin 代码里赋值。

这里的响应式开发强调一种基于观察者模式的开发方式: View 订阅 ViewModel 暴露的响应式接口,接收到通知后进行相应逻辑,而 ViewModel 不再持有任何形式的 View 的引用,减少耦合,提高了可复用性。

另外如果使用 LiveData 的话, ViewModel 对 View 层仅暴露 LiveData 接口,在 View 层不允许直接更新 LiveData, 因为一旦 View 层拥有直接更新 LiveData 的能力,就无法约束 View 层进行业务处理的行为:

class LoginViewModel : ViewModel() {
    private val _loginResult: MutableLiveData<Boolean> = MutableLiveData()
    val loginResult: LiveData<Boolean> = _loginResult
}

关于 flow 之前有在掘金上写过一篇学习笔记,感兴趣可以看看: Kotlin协程之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 层暴露 login 方法,并提供 LiveData 数据表示登录状态,让 View 层订阅

class LoginViewModel : ViewModel(), CoroutineScope by MainScope() {
    private val loginModel = LoginModel()

    private val _loginResult: MutableLiveData<Int> = MutableLiveData()
    val loginResult: LiveData<Int> = _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

Repository 模式的概念来自于领域驱动开发(Domain Driven Design)。主要思想是通过抽象一个 Repository 层,对业务(领域)层屏蔽不同数据源的访问细节,业务层(可能是 ViewModel)无需关注具体的数据访问细节。

Repository 内部实现了对不同数据源(DataSource)的访问,典型的 DataSource 包括远程数据, Cache 缓存, Database 数据库等,可以用不同的 Fetcher 来实现, Repository 持有多个 Fetcher 引用。

因此上面实例中的 LoginModel 可以换成 LoginRepository 类, LoginRepository 不暴露具体的数据访问方式,只暴露出这一能力的接口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MVCMVPMVVM 是三种设计模式,用于在 Android 应用程序中组织代码。 MVC(模型-视图-控制器):模型存储应用程序数据,视图显示数据,控制器处理用户交互。 MVP(模型-视图-presenter):与 MVC 类似,但 presenter 更加紧密地与视图绑定,负责更新视图。 MVVM(模型-视图-视图模型):与 MVP 类似,但视图模型与视图绑定,负责将数据转换为视图可用的形式。 三者的差异在于MVC会产生视图和模型之间的耦合,MVPMVVM是将视图和模型之间的耦合分离,更加灵活。 ### 回答2: Android开发中,MVCMVPMVVM都是常见的架构模式,用来组织Android应用的代码结构,让代码更加规范、易于维护。 MVC(Model-View-Controller)是最早的一种架构模式,它将应用分为三个模块:模型(Model)、视图(View)和控制器(Controller)。其中模型负责数据的存储和操作,视图负责显示界面,控制器则负责对用户输入进行响应,协调模型和视图之间的关系。MVC模式的优点是结构清晰,各个模块职责分明,易于实现代码复用,因此广泛应用。但是MVC模式也有一些缺点,比如控制器中很难进行单元测试,代码复杂度较高,难以维护大型项目等问题。 MVP(Model-View-Presenter)是一种基于MVC模式的改进,它将模型和视图分离,通过在中间加上Presenter来连接两者。Presenter接受用户的输入,并根据视图的状态更新数据模型,然后更新视图显示。MVP模式的优点是易于单元测试,将业务逻辑和界面分离,代码复杂度较低,易于维护。但是对于大型项目,Presenter层也会变得庞大且复杂。 MVVM(Model-View-ViewModel)是一种结合数据绑定和命令模式的前端设计模式,它将模型、视图和ViewModel分开,通过数据绑定将视图和ViewModel联系起来。ViewModel封装了视图的状态和行为,当ViewModel被修改时,视图会自动更新。MVVM模式的优点是将视图和ViewModel解耦,通过数据绑定自动更新视图,提高了代码的可重用性。但MVVM模式需要使用大量的数据绑定,可能导致系统卡顿,同时实现较为复杂。 总的来说,MVCMVPMVVM这三种模式都有各自的适用场景。在小型项目中,可以使用MVC模式;在中型项目中,可以使用MVP模式;在大型项目中,可以使用MVVM模式。选择合适的架构模式能够让代码更易于维护,提高开发效率。 ### 回答3: Android是一种以Java为基础的开源操作系统,广泛应用于移动设备中。在开发Android应用程序时,常用的三种架构模式是MVCMVPMVVMMVC是一种典型的应用程序架构模式,其中M代表模型,V代表视图,C代表控制器。在Android中,MVC通常用规定ViewController或Activity来实现。 MVP是Model-View-Presenter的缩写,其中M代表模型,V代表视图,P代表演示者。MVP将视图项分离,并引入中间者Presenter,以实现界面和业务逻辑分离的目的。在Android中,MVP通常实现在Activity或Fragment上。 MVVM是Model-View-ViewModel的缩写,其中M代表模型,V代表视图,VM代表视图模型。ViewModel担任中间件角色,处理视图中的数据,并使控制逻辑与视图分离开。在Android中,MVVM通常实现了Data Binding。 总的来说,三种架构模式都旨在将应用程序分离成各个组成部分,每个部分具有各自分离的职责,在开发Android应用程序时选择合适的架构模式,能够提高开发效率、提高代码质量、降低维护成本、提高整个应用程序的可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值