[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再次简化,想到两种方式:
通过使用一个Presenter代理的方式,在PresenterProxy中处理各种事件机制,View中维护一个PresenterProxy对象当然Presenter中同样实现了真实对象Presnter所实现的接口,这样,我们同样在View中通过代理对象调用真实对象的代码,结构图如下:
为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相关操作的方法,如toast
、dialog
、cancelDialog
等。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
还需要子类再来重写。
然后重写了showLoading
和cancelLoading
,在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
中调用controller
的bindEvents()
,这样,我们在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
中我们通过KController
中getView()
函数获取到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
中,Presenter
和Model
还是与之前一样的职责。
关于Presenter
的测试,只需mock一个LoginViewer
实现类即可。
第四阶段:
MVVM,把Presenter
改成ViewModel
,它与View
之间的交互可以使用Data Binding
的方式双向进行,也就是说View
和ViewModel
任意一方的改变都会体现在另一方中,Google IO上提供的框架暂时还不成熟,只支持单向,所以暂时还没有在正式的项目中使用。
实质上MV*的思想都是一样的,解耦隔离视图(View)和模型(Model),在实际的应用中不需要给`MVC`、`MVP`和`MVVM`一个明确的界限,甚至可以把几者融合在一起。
[Android]对MVC和MVP的总结的更多相关文章
- Android中MVC、MVP、MVVM具体解释
前言 今天有时间就刚好有想写关于这几个名词.对于我来说.事实上这么多名词.思想归根究竟就是要依据项目实际.人员配置来做合理优化,既不能纸上谈兵.又不能畏惧不前.那么合理分阶段架构和完好代码才是关键,本 ...
- android中MVC,MVP和MVVM三种模式详解析
我们都知道,Android本身就采用了MVC模式,model层数据源层我们就不说了,至于view层即通过xml来体现,而 controller层的角色一般是由activity来担当的.虽然我们项目用到 ...
- Android之MVC、MVP、MVVM
本文将详细阐述以下MVC.MVP.MVVM三种理念的定义 MVC MVC全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个 基本部分:模型(Model ...
- android 学习mvc 和 mvp 和 mvvm参考项目
githup地址:https://github.com/ivacf/archi 阿尔奇 此存储库展示并比较可用于构建Android应用程序的不同架构模式.完全相同的示例应用程序使用以下方法构建三次: ...
- 认清Android框架 MVC,MVP和MVVM
编者按:现在很多时候,我们都是面向搜索(或 Google 或百度).GitHub 编程,那么,在早期没有互联网的情况下,该如何学习编程,成为一名真正的开发者?亦或是作为一名小白,如何进入互联网编程时代 ...
- Android App的架构设计:从VM、MVC、MVP到MVVM
随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...
- Android开发模式之MVC,MVP和MVVM的简单介绍与区别
相信大家对MVC,MVP和MVVM都不陌生,作为三个最耳熟能详的Android框架,它们的应用可以是非常广泛的,但是对于一些新手来说,可能对于区分它们三个都有困难,更别说在实际的项目中应用了,有些时候 ...
- Android 程序架构: MVC、MVP、MVVM、Unidirectional、Clean...
摘选自:GUI 应用程序架构的十年变迁:MVC.MVP.MVVM.Unidirectional.Cleanhttps://zhuanlan.zhihu.com/p/26799645 MV* in An ...
- 从Script到Code Blocks、Code Behind到MVC、MVP、MVVM
刚过去的周五(3-14)例行地主持了技术会议,主题正好是<UI层的设计模式——从Script.Code Behind到MVC.MVP.MVVM>,是前一天晚上才定的,中午花了半小时准备了下 ...
随机推荐
- ModelDataExchange - Import
ModelDataExchange - Import eryar@163.com Abstract. The ModelDataExchange import utility enables the ...
- jQuery源码 Ajax模块分析
写在前面: 先讲讲ajax中的相关函数,然后结合函数功能来具体分析源代码. 相关函数: >>ajax全局事件处理程序 .ajaxStart(handler) 注册一个ajaxStart事件 ...
- MySQL学习笔记七:存储引擎
1.MySQL存储引擎的设计采用“插件式”方案,用户可以很方便地选择使用哪种存储引擎,想使用mysql没有提供的引擎时,可以自己安装进去. 查看支持的存储引擎 mysql> show engin ...
- Target-Action回调模式
前面的博客中提到过回调的概念,是在OC通过协议来实现的回调,和Java中的接口的回调极为相似,下面来介绍另一种方法回调模式: Target-Action回调.首先我们来从字面意思来理解一下Target ...
- 构建自己的PHP框架--实现Model类(1)
在之前的博客中,我们定义了ORM的接口,以及决定了使用PDO去实现.最后我们提到会有一个Model类实现ModelInterface接口. 现在我们来实现这个接口,如下: <?php names ...
- geotrellis使用(三)geotrellis数据处理过程分析
之前简单介绍了geotrellis的工作过程以及一个简单的demo,最近在此demo的基础上实现了SRTM DEM数据的实时分析以及高程实时处理,下面我就以我实现的上述功能为例,简单介绍一下geotr ...
- 【jQuery小实例】---2自定义动画
---本系列文章所用使用js均可在本博客文件中找到 本节用jQuery完一个简易的动画效果,一个小驴跑跑的效果.和一个类似qq面板效果.大致也分为三步:添加jquery-1.8.3.js文件.这个是不 ...
- [Data Structure & Algorithm] Hash那点事儿
哈希表(Hash Table)是一种特殊的数据结构,它最大的特点就是可以快速实现查找.插入和删除.因为它独有的特点,Hash表经常被用来解决大数据问题,也因此被广大的程序员所青睐.为了能够更加灵活地使 ...
- (十三)WebGIS中工具栏的设计之命令模式
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 从这一章节开始我们将正式进入WebGIS的工具栏中相关功能的 ...
- JS实现-页面数据无限加载
在手机端浏览网页时,经常使用一个功能,当我们浏览京东或者淘宝时,页面滑动到底部,我们看到数据自动加载到列表.之前并不知道这些功能是怎么实现的,于是自己在PC浏览器上模拟实现这样的功能.先看看浏览效果: ...