[Android]对MVC和MVP的总结


以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5036289.html

经历过的客户端的架构分为这么几个阶段:

第一阶段

使用传统的MVC,其中的View,对应的是各种Layout布局文件,但是这些布局文件中并不像Web端那样强大,能做的事情非常有限;Controller对应的是Activity,而Activity中却又具有操作UI的功能,我们在实际的项目中也会有很多UI操作在这一层,也做了很多View中应该做的事情,当然Controller中也包含Controller应该做的事情,比如各种事件的派发回调,而且在一层中我们会根据事件再去调用Model层操作数据,所以这种MVC的方式在实际项目中,Activity所在的Controller是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。

第二阶段

使用MVC的进化版——MVP,MVP中把Layout布局和Activity作为View层,增加了Presenter,Presenter层与Model层进行业务的交互,完成后再与View层交互(也就是Activity)进行回调来刷新UI。这样一来,所有业务逻辑的工作都交给了Presenter中进行,使得View层与Model层的耦合度降低,Activity中的工作也进行了简化。但是在实际项目中,随着逻辑的复杂度越来越大,Activity臃肿的缺点仍然体现出来了,因为Activity中还是充满了大量与View层无关的代码,比如各种事件的处理派发,就如MVC中的那样View层和Controller代码耦合在一起无法自拔。

第三阶段

也是现在正在使用的架构,针对第二阶段进行优化,为了把View再次简化,想到两种方式:

  1. 通过使用一个Presenter代理的方式,在PresenterProxy中处理各种事件机制,View中维护一个PresenterProxy对象当然Presenter中同样实现了真实对象Presnter所实现的接口,这样,我们同样在View中通过代理对象调用真实对象的代码,结构图如下:
  2. 为MVP增加一层专门用于处理各种的事件派发Controller层,Controller的作用仅仅是处理事件并根据事件通过维护的Presenter对象派发到对应的业务中,也就是说View层只有一个Controller的对象,View层不会主动去调用Presenter层,但是Controller层和Presenter都可能会回调到View层来刷新UI,所以层次结构就变成了如下:

现在使用的是第2种方式,使用Controller来进行对Activity中事件代码的分离,下面使用登录的例子来讲解,其中代码使用的并不是Java,而是Kotlin。

在演示之前,先来看下实现MVP的几个基础的接口和类(点这里查看AKBMVPExt.kt):

/**
 * MVP的View层,UI相关,Activity需要实现该interface
 * 它会包含一个Presenter的引用,当有事件发生(比如按钮点击后),会调用Presenter层的方法
 */
public interface KViewer {
    //    val onClickListener: ((view: View) -> Unit)?
    val context: Context?;

    fun toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
        context?.lets { Toast.makeText(this, message, duration).show() }
    }

    fun dialog(title: String? = null,
               message: String?,
               okText: String? = "OK",
               cancelText: String? = null,
               positiveClickListener: ((DialogInterface, Int) -> Unit)? = null,
               negativeClickListener: ((DialogInterface, Int) -> Unit)? = null
    ) {
        context?.lets {
            AlertDialog.Builder(this)
                    .setTitle(title)
                    .setMessage(message)
                    .setPositiveButton(okText, positiveClickListener)
                    .setNegativeButton(cancelText, negativeClickListener)
                    .show()
        }
    }

    fun showLoading(message: String) {
        Log.w(KViewer::class.java.simpleName, "loadingDialog should impl by subclass")
    }

    fun cancelLoading() {
        Log.w(KViewer::class.java.simpleName, "cancelLoadingDialog should impl by subclass")
    }

    fun <T : View> findView(resId: Int): T;

}

所有View层的Activity、Fragment或者View都要实现KViewer接口,该接口中有一个属性和一个函数需要被子类的Activity实现:

  • context属性:该属性需要被子类override,该属性用于一些接口公用的UI相关操作的方法,如toastdialogcancelDialog等。
  • fun <T : View> findView(resId: Int): T函数:该函数需要被子类Activity、Fragment或者View实现,这个方法用于从当前View层中根据id获取到对应的View,该方法在Activity、Fragment或者View中并不一致。

当然所有的重写都可以在BaseActivity、BaseFragment、BaseFrameLayout等中重写,之后使用它们的子类即可。

/**
 * MVP的Presenter层,作为沟通View和Model的桥梁,它从Model层检索数据后,返回给View层,它也可以决定与View层的交互操作。
 * 它包含一个View层的引用和一个Model层的引用
 */
public open class KPresenter<V : KViewer>(var viewer: V) {

    open public fun closeAll() {
        Log.w(KViewer::class.java.simpleName, "closeAll in KPresenter should impl by subclass")
    }

}

KPresenter类是作为所有Presenter层的实现的基类的,它只有一个closeAll函数需要被重写,当Activity在被destory时,需要调用close函数停止到子线程的任务。

/**
 * Controller,用于派发View中的事件,它在根据不同的事件调用Presenter
 */
public abstract class KController<KV : KViewer, KP : KPresenter<*>>(val viewer: KV, presenterCreate: () -> KP) {
    protected val presenter: KP by lazy { presenterCreate() }

    private val viewCache: SparseArray<View> = SparseArray();

    /**
     * 注册事件
     */
    abstract fun bindEvents()

    public fun <T : View> getView(resId: Int): T {
        val view: View? = viewCache.get(resId)
        return view as T? ?: viewer.findView<T>(resId).apply {
            viewCache.put(resId, this)
        }
    }

    public fun closeAll() = presenter.closeAll()
}

同样KController是所有Controller类的基类,需要子类实现bindEvents()函数,在这个函数中,可以绑定各种View的事件。还提供了getView()方法来从Viewer中获取到对应的控件,并且会缓存找到的控件。

一、创建BaseActivity并实现KViewer

open class BaseActivity : AppCompatActivity(), KViewer {
    override fun <T : View> findView(resId: Int): T = findViewById(resId) as T

    override val context: Context = this

    open val controller: KController<*, *>? = null

    private val loadingDialog: ProgressDialog by lazy { ProgressDialog(this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 强制竖屏
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    }

    override fun showLoading(message: String) {
        loadingDialog.setMessage(message)
        loadingDialog.show()
    }

    override fun cancelLoading() {
        if (loadingDialog.isShowing) {
            loadingDialog.cancel()
        }
    }

    override fun onDestroy() {
        controller?.closeAll()
        super.onDestroy()
    }
}

这里我重写了KViewer中的findView()函数,函数实现是通过Activity::findViewById()的方式。

又实现了controller属性,设置为null,这个controller还需要子类再来重写。

然后重写了showLoadingcancelLoading,在onDestory中通过controller调用presenter中的closeAll函数。

二、实现LoginActivity

新建LoginViewer接口,继承KViewer,并定义各种逻辑回调:

interface LoginViewer : KViewer {
    fun onLogin()
}

里面所有的函数应该都是名字onXXX的函数,都是需要去操作UI的,这里定义的是一个onLogin()函数,表示登录成功后,我们现在是如果登录成功后,则跳转到主界面MainActivity

然后创建LoginActivity,实现我们的LoginViewer

class LoginActivity : BaseActivity(), LoginViewer {
    override val controller: LoginController by lazy { LoginController(this) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        controller.bindEvents()
    }

    override fun onLogin() {
        toActivity<MainActivity> { }
        finish()
    }
}

这里我们首先重写父类中的controller属性,通过lazy懒初始化LoginController,然后在onCreate中调用controllerbindEvents(),这样,我们在controller中的bindEvents()函数中就可以对各种View进行事件的绑定,甚至包括自定义的Dialog、PopupWindow等组件的回调。

然后实现onLogin函数,在这个函数中进行界面的跳转。

三、实现LoginController

创建LoginController,继承KController

class LoginController(viewer: LoginViewer) : KController<LoginViewer, LoginPresenter>(viewer, { LoginPresenter(viewer) }),
        View.OnClickListener {

    override fun bindEvents() {
        getView<Button>(R.id.activity_login_submit_btn).setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.activity_login_submit_btn -> presenter.login(getView<EditText>(R.id.activity_login_username_et).text.toString(), getView<EditText>(R.id.activity_login_password_et).text.toString())

        }
    }

}

实现KController中的bindEvents函数,在bindEvents中我们通过KControllergetView()函数获取到Id为R.id.activity_login_submit_btn的按钮,然后设置OnClickListener,在onClick回调方法中,Controller会根据事件派发到Presenter来进行真正的登录操作。

四、实现Presenter

创建Presenter,继承KPresenter

class LoginPresenter(viewer: LoginViewer) : KPresenter<LoginViewer>(viewer) {
    fun login(username: String, password: String) {
        viewer.showLoading(_resString(R.string.xr_hint_logging_in))

        HttpsUrl(HttpWebApi.System.LOGIN).rxRequest {
            it.posts(
                    "username" to username,
                    "password" to password
            )
        }
                .map {
                    _gson._fromJson<LoginHttpResponse>(it.body().string())
                }
                .observeOnMain()
                .doOnNextOrError { viewer.cancelLoading() }
                .subscribe ({
                    if (it.success) {
                        viewer.onLogin()
                    } else {
                        showHint(it.msg)
                    }
                }) {
                    Log.e("Login", "", it)
                    viewer.toast(_resString(R.string.xr_error_default))
                }
                .bindPresenter(this)
    }
}

编写login()函数,然后执行登录请求,登录成功后,通过viewer回调到View层的onLogin()函数。

如此一来,View层中只负责UI部分的工作,UI所产生的各种事件绑定、派发等职责放在Controller中,PresenterModel还是与之前一样的职责。

关于Presenter的测试,只需mock一个LoginViewer实现类即可。

第四阶段:

MVVM,把Presenter改成ViewModel,它与View之间的交互可以使用Data Binding的方式双向进行,也就是说ViewViewModel任意一方的改变都会体现在另一方中,Google IO上提供的框架暂时还不成熟,只支持单向,所以暂时还没有在正式的项目中使用。

实质上MV*的思想都是一样的,解耦隔离视图(View)和模型(Model),在实际的应用中不需要给MVCMVPMVVM一个明确的界限,甚至可以把几者融合在一起。

时间: 2024-09-23 12:18:29

[Android]对MVC和MVP的总结的相关文章

Android开发模式之MVC,MVP和MVVM的简单介绍与区别

相信大家对MVC,MVP和MVVM都不陌生,作为三个最耳熟能详的Android框架,它们的应用可以是非常广泛的,但是对于一些新手来说,可能对于区分它们三个都有困难,更别说在实际的项目中应用了,有些时候想用MVP的,代码写着写着就变成了MVC,久而久之就对它们三个的选择产生了恐惧感,如果你也是这样的人群,那么这篇文章可能会对你有很大的帮助,希望大家看完都会有收获吧! 文章重点: (1)了解并区分MVC,MVP,MVVM. (2)知道这三种模式在Android中如何使用. (3)走出data bin

Android开发中的MVP架构

最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解MVP和DDD,但是我们的新项目还是决定通过MVP来构建. 这篇文章是我通过研究和学习各种文章以及专题讨论所总结出来的,它包括以下几点: 为什么越来越多的人开始关注架构? 首先,MVP是什么? 哪种架构才是最好的,MVC,MVVM还是MVP? MVP的利与弊 Show me the code!!!代码展示 不幸的,这篇文章将不包括: 详细生动的代码示例 如何编写测试代码 最后,我将告诉你如何更进一步学习这些专题.

不必纠结MVC还是MVP了,听我说两句~

MVC全称是Model-View-Controller 也就是模型–视图–控制器.是在1970年的时候提出由TrygveReenskaug在Smalltalk-80系统上首次提出的. SmallTalk在百度百科的解释是这样: Smalltalk被公认为历史上第二个面向对象的程序设计语言和第一个真正的集成开发环境 (IDE). 来张图说明一下MVC的工作模式吧   图中红色小框框就是MVC的工作模式 从图中可以看出用户向View发送指令,再有View直接要求Modle改变状态 用户也可以直接向C

Android应用架构之MVP实现

回顾上一篇文章<Android应用架构概述>,我们知道,Android App 本质上抽象成两个层次:视图和数据.为了App在发展过程中快速的适应变化,方便维护和快速迭代,我们要将数据和视图解耦,而在解藕方面我们的前辈们在漫长的软件开发经验中为我们提供了两套流行的指导框架:MVC和MVP,其中MVP近年来在Android应用开发上逐渐流行.接着上一篇的内容,本章我将结合具体例子说清MVP解藕的实现.所以本章的思路是:以登录为业务场景,分析对比"非MVP"和MVP的实现方式.

【框架篇】mvc、mvp、mvvm使用关系总结

MVC MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑.MVC被独特的发展起来用于映射传统的输入.处理和输出功能在一个逻辑的图形化用户界面的结构中. 数据关系 View 接受用户交互请求 View 将请求转交给Controller Controller

MVC、MVP、MVVM三种框架模式到底怎么理解?

问题描述 MVC.MVP.MVVM三种框架模式到底怎么理解? 如题,这三个到底该如何理解? 1.M到底只是数据,还是数据+业务逻辑?如果是前者,为何不叫Data? 2.MVP里,M对V有没有影响?是不是说,P处理后发现存储的数据需要改变,就通知M改变,显示的数据需要改变,就通知V改变? 3.MVVM与MVP相比,进步的地方在哪里? 4.对于软件开发者与WEB全栈开发者而言,这三种框架模式的意义相同吗? 看了网上很多这方面的说法,感觉众口不一,枯涩难懂,哪位前辈能彻底解惑.以正视听呢? 解决方案

界面之下:还原真实的 MVC、MVP、MVVM 模式

前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV*模式之间的区别分不清,甚至有些描述都是错误的.本文追根溯源,从最经典的Smalltalk-80 MVC模式开始逐步还原图形界面之下最真实的MV*模式. GUI程序所面临的问题 图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息.用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑

艾伟_转载:MVC和MVP的一些思考

这篇文章是我近期对MVC和MVP的一些思考,在使用MVC/MVP模式的过程中曾经走过一些弯路.呵呵,现在虽然改正了某些弯路,但不保证改正了所有的弯路(例如对渲染的理解),所以请阅读这篇文章的朋友不吝发挥你们的质疑. 写这篇文章也是想知道自己还有什么地方是错的,我的最终方案是否可行? 有交流才会有进步.你有一个苹果,我有一个苹果,我们交换后仍各有一个苹果,你有一个思想,我有一个思想,我们交换后......会有N个思想 :p 1. MVC的理解误区 以下是我以前对MVC模式的理解误区: 1. 认为M

MVC和MVP的一些思考

这篇文章是我近期对MVC和MVP的一些思考,在使用MVC/MVP模式的过程中曾经走过一些弯路.呵呵,现在虽然改正了某些弯路,但不保证改正了所有的弯路(例如对渲染的理解),所以请阅读这篇文章的朋友不吝发挥你们的质疑. 写这篇文章也是想知道自己还有什么地方是错的,我的最终方案是否可行? 有交流才会有进步.你有一个苹果,我有一个苹果,我们交换后仍各有一个苹果,你有一个思想,我有一个思想,我们交换后......会有N个思想 :p 1. MVC的理解误区 以下是我以前对MVC模式的理解误区: 1. 认为M