【Android&Kotlin】MVP、MVVM架构的终极演化之道,将业务代码的复用性提升到极致!

我就不绕弯子了,我就是来推广自己的MVP、MVVM框架的,求小星星:https://github.com/xiazunyang/brick


0.操作拆分

首先,我们将一个操作拆分成以下步骤来讲解一下从MVC到MVP,再到MVVM的演化过程。

1.用户点击屏幕。
2.显示等待动画。
3.启动后台线程加载数据。
4.获取到数据结果,关闭等待动画。
5.显示数据结果。


1.MVC (Model-View-Controller)

我们将MVC的各个角色代入到以上步骤中:

1.用户操作View产生指令。
2.View将指令传递到Controller,Controller通知View显示等待动画。
3.Controller通过Model获取数据。
4.Model将结果传递给View,并关闭等待动画。
5.View加载数据结果。

可以发现,控制等待动画的可能是Controller,也可能是Model。并且Model传递数据时,可能传递给Controller,也可能直接传递给View。把Controller的角色无限弱化,就是人人学写代码时就会使用的MVC架构了。

画成图就是这样:

在MVC中,每个界面有一套自己的Model-View-Controller,界面之间无法复用其它界面的角色。比如,在两个不同的界面中访问相同的网络接口,甚至要写两遍相同的代码。

优点:对于小项目来讲,简单易用,学习成本低。

缺点:高耦合;复用性低;不易做单元测试;项目过大时,代码非常混乱。


2.MVP(Model-View-Presenter)

把MVC中各角色的职责划分开来,让Model与View不能直接相互访问,并且始终通过中间层来访问,就是我们常用的MVP架构了。

将MVP的各个角色代入后的调用过程:

1.用户操作View产生指令。
2.View将指令传递到Presenter。
3.Presenter通知View显示等待动画,并调用Model获取数据。
4.Model将结果传递给Presenter,Presenter通知View关闭等待动画。
5.Presenter将数据传递给View,加载数据结果。

画成图就是这样:

在MVP中,View与Model都是负责原子性的操作,由Presenter将它们的动作连接起来完成一个完整的流程,在大多数MVP框架中,1个界面实现1个View,每个View持有一个Presenter,每个Presenter持有多个Model,由此来实现Model的复用。

优点:提高了代码的复用性;可以方便地进行单元测试。

缺点:各角色初始化的代码略多;Model复用时,Presenter中依然存在重复代码;

在这种MVP架构中,Model的代码实现了复用,但是Presenter中依然存在重复的代码,这给MVP的演进留有了一定的空间。我将在下面对MVVM的讲解中,进一步完善它。


3.MVVM(View-ViewModel-Model)

从MVC到MVP,只需要划分角色职责就能实现,无须其它框架的支持。而MVVM是在MVP的基础上,还需要引入dataBinding、Lifecycler、ViewModel、LiveData等框架后才能实现,学习成本成倍的上升。

但是本文并不打算讲解dataBinding、Lifecycler、ViewModel和LiveData,因为它们每一个都是可以独立使用的框架,相关的文章大家可以另外去找。但是Lifecycler、ViewModel和LiveData最核心的特性,必需先讲一讲。

1.Lifecycle可以理解为用于监听Activity和Fragment的生命周期回调的框架,当Activity和Fragment的生命周期发生变化时,Lifecycle可以感知到变化。

2.ViewModel不会因为Activity和Fragment的配置更改(比如旋转)而被销毁,ViewModel的持有者被重新创建时,将重新连接到之前持有的ViewModel。只有当Activity和Fragment确定销毁时,ViewModel才会销毁。

3.LiveData是一个数据容器,控件可以订阅它里面的数据,订阅时需要Lifecycle作为参数,当数据发生变化时,并且在Lifecycle处于活动状态时,才会通知订阅它的控件。

这也就意味着,储存在ViewModel中的数据在Activity和Fragment销毁前是不会丢失的。

并且中间层不再需要把数据传递回View层,直接放在ViewModel里面的LiveData中即可,这样可以少定义一些View的接口。

MVP中的Presenter是没有要求继承任何类的,而ViewModel是一个非常优秀的中间层,所以让Presenter继承ViewModel是可行的!

同时这也带来了一个好处,ViewModel会在View销毁时销毁自己,这可以帮助我们减少内存泄漏的风险。

即使使用了Lifecycler、ViewModel和LiveData之后能有这么多好处,但是它与MVVM之间还差多少呢?答案是,一个dataBinding。

是的没错,MVVM与MVP的区别,仅仅是用没用dataBinding的区别(至少在Android中,是这样的)。

但是想理解MVVM,并充分发挥它的全部实力,掌握使用了Lifecycler、ViewModel和LiveData的MVP是必需的。

并且,我要彻底解决掉Presenter中依然存在重复代码的问题,要把业务代码的复用率提升到极致;并且利用Kotlin的特性,消灭掉MVP初始化代码太多的问题!最终实现如以下图中的超强复用性:


4.我的MVP/MVVM框架

框架特色:

1.1套业务代码对应1套MVVM体系,界面只需要实现业务的View接口即可,一个界面可调用多套业务代码。

2.提供Kotlin扩展方法与工厂方法来创建ViewModel,并自动为其创建Model。

3.在创建Model时,可以通过构造方法注入Retrofit Api接口实例,并且通过Kotlin代理特性,实现无代码体的Model类。

4.兼容纯Java项目。


框架依赖:

1.在项目根目录下的build.gradle文件中添加以下代码,如果已存在,则忽略此步。

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

2.在相应的模块的build.gradle文件中的适当位置添加以下代码:

    implementation 'com.github.xiazunyang.brick:brick:1.2.1'

框架使用:

1.在自己项目中的Activity和Fragment的基类中,实现IVew接口,如:

abstract class AbstractMVVMActivity : AppCompatActivity(), IView {
    
    override fun showLoading(message: String, isCancelable: Boolean) {
        //在此处显示等待动画
    }

    override fun hideLoading() {
        //在此处关闭等待动画
    }

}

IView接口继承了LifecycleOwner接口,而AppCompatActivity又是继承自ComponentActivity,而ComponentActivity是实现了LifecycleOwner接口的,所以我们不需要实现getLifecycle方法。

也正是IView接口继承了LifecycleOwner接口,我们才可以在ViewModel中感知View的生命周期。

2.在自己的Application的onCreate方法中初始化框架:

    override fun onCreate() {
        super.onCreate()
        //传入Retrofit或实现了IRetrofit接口的实例
        installRetrofit(Retrofit or IRetrofit)
    }

3.创建一个业务的View接口,要求实现IVew接口,并添加若干与业务相关的抽象方法,如:

interface MainView : IView {

    fun onLoadWeChatAuthorsFailure(throwable: Throwable)

}

4.创建一个业务的Model类。并创建Retrofit Api接口,如:

class MainModel {

    suspend fun getWeChatAuthor(): JsonResult<List<WeChatAuthor>> {
        //Retrofit访问网络的代码
    }

}

//Retrofit Api接口
interface MainApi {

    @GET("wxarticle/chapters/json")
    suspend fun getWeChatAuthor(): JsonResult<List<WeChatAuthor>>

}

通过以上代码可以发现,Model中的方法与Retrofit Api中的方法完全一致,该方法中只做了使用Retrofit创建Api接口实例的操作,我们可以使用Kotlin代理的特性,将Model中的该方法省略掉:

class MainModel(mainApi: MainApi) : MainApi by mainApi


interface MainApi {

    @GET("wxarticle/chapters/json")
    suspend fun getWeChatAuthor(): JsonResult<List<WeChatAuthor>>

}

这样一来,MainModel就直接继承了MainApi中的方法,你只需要把Retrofit Api的接口作为Model的构造方法的参数传入就可以了。框架会调用第2个步骤中设置的Retrofit或网络工具类来创建Retrofit Api的实例,并调用Model的构造方法来完成Model创建工作。

另外需要说明的是,如果你的网络请求框架并不是Retrofit,也不理解为什么要创建MainApi接口,你可以省略第2个步骤中的初始化操作,也可以省略这个步骤中的MainApi接口,但是你的Model角色必需拥有一个没有参数的构造方法(不写任何关于构造方法的代码就可以了),框架会通过这个构造方法来创建Model的实例。

不使用Retrofit Api注入功能的Model类:

class MainModel {
    
    fun getWeChatAuthors(): List<WeChatAuthor> {
        //请求网络的代码
    }

}

5.创建一个业务相关的ViewModel类,要求继承自AbstractViewModel,AbstractViewModel要求接收两个泛型参数,分别传入第3步和第4步创建的View接口和Model类。框架中提供了两个AbstractViewModel,一个在com.numeon.brick包下,一个在com.numeon.brick.coroutine包下,它们唯一的区别就是后者实现了CoroutineScope接口,如果你需要使用Kotlin协程的话,就使用com.numeon.brick.coroutine包下的AbstractViewModel。如:

class MainViewModel : AbstractViewModel<MainView, MainModel>()

6.在View接口中创建一个对应的ViewModel的属性,然后给一个默认的get方法实现,如:

interface MainView : IView {

    //以下两行是新增的代码,createViewModel是框架中提供的扩展方法
    val mainViewModel: MainViewModel
        get() = createViewModel()

    fun onLoadWeChatAuthorsFailure(throwable: Throwable)

}

此处注意,Kotlin中的接口是可以有默认实现的,虽然写作属性,在Kotlin中调用也是属性的方式,但是在编译后其实是编译成了Java中的getMainViewModel方法,你可以Java代码中调用此方法来验证。然后如果是在Java项目中,应该启动jdk1.8,然后为方法加上default关键字,然后才能给一个个默认实现代码。

7.在对应的ViewModel中创建处理业务的方法,如:


class MainViewModel : AbstractViewModel<MainView, MainModel>() {

    //用于保存结果数据的LiveData
    val weChatAuthorLiveData = MutableLiveData<List<WeChatAuthor>>()

    //业务代码示例
    fun getWeChatAuthors() {
        launch {
            //通知View显示等待框
            view.showLoading()
            try {
                //切换线程获取网络上的数据,并将结果放入weChatAuthorLiveData中
                weChatAuthorLiveData.value = model.getWeChatAuthor().result
            } catch (e: Exception) {
                e.printStackTrace()
                //当获取数据失败时,通知View
                view.onLoadWeChatAuthorsFailure(e)
            }
            //通知View关闭等待框并向View层传递结果
            view.hideLoading()
        }
    }

}

8.创建一个Activity,实现业务对应的View接口,在以下示例中,我们可以访问到MainView中的mainViewModel属性,并调用其方法:

class MainActivity : AbstractMVVMActivity(), MainView {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //监听LiveData来获取数据
        mainViewModel.weChatAuthorLiveData.observe(this, Observer { list ->
            Log.d("MainActivity", "获取到的数据:$list")
        })
        //调用mainViewModel中的方法来获取数据
        mainViewModel.getWeChatAuthors()
    }

    override fun onLoadWeChatAuthorsFailure(throwable: Throwable) {
        Log.e("MainActivity", "获取数据时发生了错误", throwable)
    }

}

以上,就是此框架最基本的使用了,使用起来相当简单,你不需要关心太多东西,只需要按照约定的格式定义完MVP、MVVM中的各个角色类,就可以了。


1.建议允许Model被继承,这样可以减少Model之间复用的代码量。

2.如果使用继承无法满足需求,或者不想使用继承实现,框架提供了createModel方法,用于创建Model的实例。

3.因为View是接口,Activity和Fragment可以实现多个View接口,由此提升了业务代码的复用性。


最后,只要项目启用了dataBinding,你的项目就是一个完全体的MVVM架构了。

Kotlin示例及框架源码:https://github.com/xiazunyang/brick

纯Java项目也能用,只是少了by关键字,Model对Retrofit Api的调用不能像Kotlin那么方便了。

查看纯Java示例:https://github.com/xiazunyang/BrickJavaDemo

最后,感谢WanAndroid提供的网络接口。

猜你喜欢

转载自blog.csdn.net/xiazunyang/article/details/102470351