转载于:Android中MVP框架理解 - AND_Devil - 优快云博客
概述
对于MVP,本人有问题:
为何这个模式出来后,就能被广大的Android的程序员接受呢?
一 关于MVC
- 关于MVC
- MVC 全称是Model - View - Controller,是模型(model)-视图(view)-控制器(controller)的缩写。
- Model:业务逻辑和数据处理(数据库存储操作,网络数据请求,算法,耗时操作…),专为存储和管理应用数据而生。
- View:对应于布局文件(凡是能够在屏幕上看见的对象,都是视图对象)。
- Controller:用户交互(读取View视图,向Moder发送数据请求,让数据显示在界面上),包括响应View对象触发的各类事件和管理着模型对象和视图层间的数据流动。
的确像那么回事,但是,其实View对应于布局文件来说的话,能做的事情特别少,关于该布局文件中的数据绑定的操作,事件处理的代码全部都在Activity中,造成了Activity既像View又像Controller(当然了Data-Binder的出现,可能会让View更像View吧),这就造成了activity类的代码量巨大,维护起来特别麻烦!
优点:Model层和View层不直接交互,把UI界面和业务逻辑、数据处理分离。
缺点:Activity控制器中有很多显示UI的代码,因此View视图和控制器Controller不是完全分离的,也就是说View视图和控制器Controller绑定在一个类中,当布局复杂的时候,Activity会非常冗余繁杂,解耦不完美。
1.视图与控制器间的过于紧密的连接
视图与控制器是相互分离,但却是联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
2.视图对模型数据的低效率访问
依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
二 关于MVP
- MVP 全称是Model - View - Presenter ,是模型(model)-视图(view)-呈现器(presenter)的缩写。
- Moder:业务逻辑和数据处理(数据库存储操作,网络数据请求,复杂算法,耗时操作)。
- View : 对应于Activity,负责View的绘制以及与用户交互
- Presenter:负责完成View于Model间的交互 (有一点还需要注意,presenter是双向绑定的关系,因此,在设计的时候就要注意接口和抽象的使用,尽可能的降低代码的耦合度,这也是mvp的宗旨)
逻辑:将Activity(也就是将View和Controller合并为View)作为View,Model不变,并添加Presenter;View和Model不直接交互,而是使用Presenter作为桥梁。Presenter同时拥有View和Model的Interface引用,而View层有Presenter的Interface引用。当View层需要展示数据的时候,会调用Presenter层的接口,然后Presenter会调用Model请求数据,当Model层数据加载成功后会调用Presenter的回调方法通知Presenter层数据加载完毕,最后Presenter层会调用View层的接口将加载的数据展示给用户。
优点:将View和Moder完全分离,互不依赖。
缺点 : 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。还有一点需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了
三 MVP 与 MVC 区别
- MVC中的Moder、View和Controller两两之间都有联系。
- MVP中的Moder与View不能直接联系,只能通过Presenter发生关系。
最明显的区别就是,MVC中是允许Model和View进行交互的,MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的。
四 登录经典示例说明MVP架构
项目结构:
(一)Model
创建User模型实体类:
package com.example.mvp_login.Model;
/**
* create by AND
* 封装用户信息
*/
public class User {
private String username;
private String userpassword;
public User(String username, String userpassword) {
this.username = username;
this.userpassword = userpassword;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", userpassword='" + userpassword + '\'' +
'}';
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
创建Model业务逻辑接口类IUserLogin :
package com.example.mvp_login.Model;
/**
* 用户登录接口:定义一个业务逻辑的实现方法
*/
public interface IUserLogin {
/**
* @param user 登录需要的用户信息
* @param onLoginListener 登录需要的监听事件
*/
void login(User user, OnLoginListener onLoginListener);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
定义Model业务逻辑实现类
package com.example.mvp_login.Model;
/**
* 用户登录的具体实现类,实现定义的接口,毕竟,接口里方法是没有办法直接使用的
* 在这里实现具体的登录操作
*/
public class UserLogin implements IUserLogin {
@Override
public void login(final User user, final OnLoginListener onLoginListener) {
//模拟子线程耗时操作
new Thread() {
@Override
public void run() {
super.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟登录成功
if (user.getUsername().equals("devil") && user.getUserpassword().equals("123456")) {
//登录成功
onLoginListener.loginSuccess();
} else {
//登录失败
onLoginListener.loginFailed();
}
}
}.start();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
定义Model成功失败回调类接口OnLoginListener:
package com.example.mvp_login.Model;
/**
* 用户登录是否成功的监听
* 里面的方法不需要参数,也不需要回传数据,只需随时调用
*/
public interface OnLoginListener {
//登录成功
void loginSuccess();
//登录失败
void loginFailed();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
实体类,业务类接口,业务类,login()方法一般是连接服务器的,是个耗时操作,所以开辟子线程,Thread.sleep(2000)模拟耗时,由于是耗时操作,所以通过一个回调接口来通知登录的状态。
( 二 ) View
Presenter与View交互是通过接口。所以我们这里需要定义一个ILoginView,难点就在于应该有哪些方法
package com.example.mvp_login.View;
/**
* view层,顾名思义,视图层,针对的是activity和fragment;
* 负责从视图中获取相应的数据和更新UI
*/
public interface UserLoginView {
//显示进度条
void showLoadding();
//隐藏进度条
void hideLoadding();
//获取用户输入的信息
String getUserName();
//获取用户输入的信息
String getUserPassword();
//登陆成功
void toActivity();
//登陆失败了
void loadFailed();
//重置信息
void clearUserMessage();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
有了接口,实现就太好写了~~~
观察功能上的操作,然后考虑:
- 该操作需要什么?(getUserName, getPassword)
- 该操作的结果,对应的反馈?(toMainActivity, showFailedError)
- 该操作过程中对应的友好的交互?(showLoading, hideLoading)
创建xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.mvp_login.MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="登录"
android:textSize="25sp" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户名"
android:textSize="20sp" />
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码"
android:textSize="20sp" />
<EditText
android:id="@+id/userpassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录" />
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重置" />
</LinearLayout>
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible" />
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
View的实现类,就是Activity,其实MVP中的View其实就是Activity|Fragment。
package com.example.mvp_login;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.example.mvp_login.Presenter.UserLoginPresenter;
import com.example.mvp_login.View.UserLoginView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, UserLoginView {
private EditText mUsername;
private EditText mUserpassword;
private Button mLogin;
private Button mClear;
private ProgressBar mProgress;
private UserLoginPresenter userLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();//初始化视图
}
private void initView() {
mUsername = (EditText) findViewById(R.id.username);
mUserpassword = (EditText) findViewById(R.id.userpassword);
mLogin = (Button) findViewById(R.id.login);
mLogin.setOnClickListener(this);
mClear = (Button) findViewById(R.id.clear);
mClear.setOnClickListener(this);
mProgress = (ProgressBar) findViewById(R.id.progress);
//获取P层的实例
userLoginPresenter = new UserLoginPresenter(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
//登录操作
case R.id.login:
// TODO 18/01/06
//调用P层的登录方法
userLoginPresenter.login();
break;
case R.id.clear:
// TODO 18/01/06
//调用P层的重置方法
userLoginPresenter.clear();
break;
default:
break;
}
}
@Override
public void showLoadding() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mProgress.setVisibility(View.VISIBLE);
}
});
}
@Override
public void hideLoadding() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mProgress.setVisibility(View.INVISIBLE);
}
});
}
@Override
public String getUserName() {
return mUsername.getText().toString();
}
@Override
public String getUserPassword() {
return mUserpassword.getText().toString();
}
@Override
public void toActivity() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "Login Success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void loadFailed() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "Login Failed", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void clearUserMessage() {
mUsername.setText("");
mUserpassword.setText("");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
Activity中实现我们定义的业务逻辑接口,然后接口引导我们去完成操作。
(三)Presenter
Presenter是用作Model和View之间交互的桥梁
其实也是主要看该功能有什么操作,本例只有两个操作:login()和clear()。
package com.example.mvp_login.Presenter;
import com.example.mvp_login.Model.IUserLogin;
import com.example.mvp_login.Model.OnLoginListener;
import com.example.mvp_login.Model.User;
import com.example.mvp_login.Model.UserLogin;
import com.example.mvp_login.View.UserLoginView;
/**
* author:Created by AND on 2018/1/7.
* 连接view层和model的桥梁,--->中介的性质
* Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?
* 其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。
*/
public class UserLoginPresenter {
private IUserLogin userLogin;//model层
private UserLoginView userLoginView;//view层
//v层的有参构造--->因为viewactivity调用时只需要传递上下文
public UserLoginPresenter(UserLoginView userLoginView) {
this.userLoginView = userLoginView;
this.userLogin = new UserLogin();
}
//供activity调用的登录方法
public void login() {
//登录开始,显示进度条
userLoginView.showLoadding();
User user = new User(userLoginView.getUserName(), userLoginView.getUserPassword());
//m层登录业务操作类
userLogin.login(user, new OnLoginListener() {
@Override
public void loginSuccess() {
//登录成功
userLoginView.toActivity();
//隐藏进度条
userLoginView.hideLoadding();
}
@Override
public void loginFailed() {
//登录失败
userLoginView.loadFailed();
//隐藏进度条
userLoginView.hideLoadding();
}
});
}
//供activity调用的重置方法
public void clear() {
userLoginView.clearUserMessage();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
presenter完成二者的交互工作,那么就需要二者的实现类。大致流程就是从View中获取需要的参数,交给Model去执行具体业务方法,执行的过程中需要的反馈,以及结果,再让View进行对应的显示和页面更新。
五 为什么说MVP是优秀的架构模式?
大家对于MVP的普遍的认识其实还是:“代码很清晰,只不过增加了很多类”。但是回过头来,让你自己想个例子写,就头疼的写不出来
这个模式的确让代码的清晰度有了很大的提升。
总结起来,就这么几点
优点
- 低耦合:MVP拆分了MVC臃肿的Activity,独立了Model、View、Presenter,并通过接口的方式进行连接,板块化实现了低耦合。
- 高复用:Presenter、Model与Activity(View)的关系实现了一对多。
- 易测试:独立了Model、View、Presenter以后,更加方便进行单元测试。
- 好维护:低耦合就成就了好维护。
缺点
由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。还有一点需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了
- 1