Android开发——MVP模式实现

Stars-one 2020年08月27日 493次浏览 本篇字数为0字

本文为作者原创,转载请注明出处,谢谢配合
作者:Stars-one
链接:https://stars-one.site/2020/08/27/android开发mvp模式实现


本文例子采用Kotlin进行编写,需要有一定的Kotlin基础

最近接触了公司的Android项目,采用的是MVP模式架构,之前倒还没用过MVP模式,这次来便是来学学

1.MVP模式优势及缺点

MVP架构图

  • View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity);
  • View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;
  • Model 负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);
  • Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

稍微形象地解释,View和Presenter这个是中间人不干活,只负责喊话,而负责干活的就是View接口和Model

当用户点击了按钮(或者是其他屏幕交互动作),触发了相关的事件操作,View就对Presenter喊话了:“现在需要进行登录操作,你去叫人。”

Presenter收到之后,会去获取相关需要的数据,之后去喊话Model:“现在需要登录操作,用户名是xx,密码是xx。”

Model接收数据处理之后,对Presenter回话:"这个密码不正确,登录失败了!"

Presenter之后拿到Model返回的数据,之后再去View接口喊话:"登录失败了,密码不正确,你把消息通过弹窗显示给用户看吧"

优势

  • 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle
  • 模块职责划分明显,层次清晰
  • 隐藏数据
  • Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下)
  • 利于测试驱动开发,由于Model中的业务逻辑与View高度分离,我们可以编写相关的单元测试来测测试我们的功能
  • View可以进行组件化。在MVP当中,View不依赖Model。View只需要提供一系列接口提供给上层操作,可以实现高度可复用的View组件
  • 代码灵活性

缺点

  • Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
  • 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。
  • 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了
  • 额外的代码复杂度及学习成本

总之,凡事都有双面性,不要为了设计而设计,为了架构而架构,需要考虑项目的实际情况!

架构的最终目的是实现一个快速开发并后期维护的软件,当使用某种架构会使得项目复杂且后期维护困难,那么就不该使用此架构。

2.简单Demo实现

网上的资料我看了几篇,但发现都是顺序有点乱,这里以一个登录操作功能为例,先实现最简单的一个Demo,之后再考虑几个优化点进行代码的优化,这样阅读起来比较好,不会太突兀

功能介绍:APP的界面需要两个输入框,用来输入用户名和密码,之后一个登录按钮用来实现登录操作。当点击按钮之后,会获取用户名和密码,进行比对之后,显示“登录成功”或“登录失败”的Toast信息

2.1 定义ILoginView接口

从上面的MVP的架构图我们可以看到,有个View的接口层,其作用主要是让Presenter进行调用,简单起见,我们声明一个showToast方法,用来展示登录成功或失败的信息

interface ILoginView {
    /**
     * 显示Toast信息
     */
    fun showToast(msg:String)
}

2.2 定义Model操作

这里,我们需要定义一个Model层,让其为Presenter提供一个登录方法

我是定义成了服务(就是相当于Model层的功能)

class UserService {
	/**
	 * 两个相匹配返回true
	 */
    fun login(name:String,pwd:String):Boolean{
        return name == "starsone" && pwd == "123456"
    }
}

2.3 定义Presenter类

之前也是说过,Presenter是不做事的,他负责喊人,他喊的人,应该是属于他管的,所以,Presenter包含View接口和Model,也就是上面我们定义的ILoginView和UserService

class LoginPresenter{
    private  val loginView:ILoginView
    private val userService = UserService()
    
    constructor(iLoginView: ILoginView){
        this.loginView =iLoginView
    }

	/**
	 * 登录操作,Activity中触发时间调用
	 */
    fun login(name: String, pwd: String) {
        if (userService.login(name, pwd)) {
            loginView.showToast("登录成功")
        } else {
            loginView.showToast("登录失败")
        }
    }
}

2.4 Activity调用Presenter

这里应该是各位Android开发非常熟悉的事情了,实例化一个Presenter对象,给按钮设置点击监听器,之后在点击事件调用Presenter对象的login方法

不过,我们还得去实现View接口里的方法,让其显示Toast即可

到这一步,我们已经实现了一个简单的MVP模式demo了。调试的时候,如果输入的用户名为starsone,密码为123456则显示登录成功的Toast,这里就不放截图了

3.优化点

上面的简单demo其实已经实现了一个MVP模式,但由于MVP模式只是提出了某种架构,但是其代码编写并没有一个统一的标准,所以导致开发者根据自己的需要对MVP模式进行的各种魔改

所以,下面就大概说说可以优化的点,大家可以根据自己的需要和实际情况对MVP模式进行魔改。

3.1 考虑复用View接口

View接口的操作很大部分都是可以复用的,比如说弹Toast提示,显示对话框,显示加载中等等操作

所以,为了复用,我们可以构造一个通用的View接口,之后定义一个BaseActivity去实现这个View接口,之后的Activity继承这个通用的BaseActivity即可,如下代码所示:

interface IBaseView{
    fun showToast(msg:String)
    fun showDialog(msg:String)
    fun showLoadingDialog(msg:String)
}

class BaseActivity : AppCompatActivity(),IBaseView {
    override fun showToast(msg: String) {
        //省略相关的代码
    }

    override fun showDialog(msg: String) {
        //省略相关的代码
    }

    override fun showLoadingDialog(msg: String) {
        //省略相关的代码
    }

}

3.2 P层也可考虑复用

之前我们是直接定义了一个Presenter的类,考虑复用的话,我们也可以定义个Presenter的接口,之后写一个通用的BasePresenter去实现Presenter的接口,和上面的逻辑差不多,之后需要的Presenter可以继承BasePresenter来使用

3.3 考虑契约层Contact

如果View和Presenter的功能较多且都是比较单一,并不适合复用的,为了方便管理,可以考虑使用功能契约层,其中包含View接口和Presenter接口,如下代码所示:

interface ILoginContact {
    interface ILoginView {
        fun showToast(msg:String)
        fun showDialog(msg:String)
        fun showLoadingDialog(msg:String)
    }

    interface ILoginPresenter {
        fun login(name: String, pwd: String)
    }
}

使用的话,可以让Activity去实现ILoginContact.ILoginView这个接口,定义个Presenter去实现ILoginContact.ILoginPresenter接口,之后使用如之前所说,在事件中调用Presenter对象的相关方法即可

3.4 考虑使用弱引用

如果在Presenter需要用到View对象(某些需求需要),则需要考虑到弱引用

class LoginPresenter{
    private var view:WeakREference<View>
	...
}

3.5 考虑接口回调优化调用

Presenter调用Model层时,可以考虑使用接口回调的方法来优化调用

如之前我们UserService中的login方法是返回一个boolean来告知Presenter是否已经登录成功,这里我们可以改造一下

登录接口定义:

interface LoginListener{
    fun loginSuccess()
    
    fun loginFail(errorMsg:String)
}

Model改造login方法:

class UserService {
	//需要传递一个LoginListener,方法不再返回boolean
    fun login(name:String,pwd:String,loginListener: LoginListener){
        if (name == "starsone" && pwd == "123456") {
            loginListener.loginSuccess()
        } else {
            loginListener.loginFail("用户和密码不正确")
        }
    }
}

Presenter调用:

class LoginPresenter {
    private val loginView: ILoginView
    private val userService = UserService()

    constructor(iLoginView: ILoginView) {
        this.loginView = iLoginView
    }

    fun login(name: String, pwd: String) {
        userService.login(name,pwd,object :LoginListener{
            override fun loginSuccess() {
                loginView.showToast("登录成功")
            }

            override fun loginFail(errorMsg: String) {
				//由于可以回传错误信息,我们可以将其通过Toast提示用户
                loginView.showToast(errorMsg)
            }
        })
        
    }
}

参考

相关标签