挑战MVP
挑战MVP
MVP.jpg前言
之前一直听说MVP,听到这种模式有多么好,但是一直没有尝试过这种模式,也没有花时间去理解它。但是听周围的朋友都在项目中使用到了这种模式,便萌生了尝试一下的念头。笨鸟,还飞得晚,活该自己落后与别人哦。来吧,下了决心,怎么也得理解一下是个什么一回事吧。总不能还停留在“最有价值球员”的认知上吧!!!
一、什么是MVP?
MVP?今年不是杜兰特吗?这个我知道啊!
逗逼.jpg这里的MVP可不再是NBA了哦,我还是醒醒吧!天天就知道不务正业。书归正传,说到了MVP,跟我们联系比较紧密的也就是MVC了,这俩哥们儿是啥关系啊,姓氏都一样,是不是哥俩儿啊?
仔细一看,还这有那么几分神似。
MVC,全称Model—View—Controller(模型-视图-控制器),这种模式是在80年代Small talk-80出现多的一种设计模式,后来被得到了广泛的应用。
MVP,全称Model—View—Presenter(模型-视图-表示器),这种模式是由IBM开发出来的一个针对C++和Java的编程模型,大概出现于2000年,普遍认为这种模式是MVC模式的一个变种。
二、MVC与MVP的对比
代码结构 | MVC | MVP |
---|---|---|
Model | 业务逻辑和实体模型 | 业务逻辑和实体模型 |
View | 对应于布局文件 | 对应于Activity,负责View的绘制以及与用户交互 |
Controller | 对应于Activity | —— |
Presenter | —— | 负责完成View与Model之间的交互 |
从上面的表格对比中可以看得出来,MVP大大的减少了Activity中的职责,简化其中代码量,将View与Model之间的交互的复杂逻辑代码都提取到了Presenter中进行处理。这样做的好处就是降低了耦合性,模块职责划分更明显,便于进行测试。将原本复杂的Model-View关系转变为更加清晰明了的Model-View关系。
- 未使用MVP模式的情况下:
- 使用MVP模式的情况下:
下图中是MVC与MVP的图示:
MVC与MVP.png两种模式最大的区别是:
- MVC中允许Model与View进行交互,也就导致了我们时常看到上千行代码的MainActivity;而MVP中将Model与View之间的交互交给Presenter完成,并且Presenter和View是通过接口完成交互的。
三、MVP的优缺点对比
MVP对比 | 优点 | 缺点 |
---|---|---|
1 | 降低耦合度,实现Model与View的完全分离 | Presenter中的逻辑处理较多,是得Presenter比较臃肿,维护比较困难 |
2 | 模块职责划分明显。层次清晰 | View的渲染放到了Presenter中,导致View与Presenter的交互过于频繁 |
3 | 隐藏了数据,安全性提高 | View与Presenter联系紧密,一旦View发生改变,Presenter也将发生改变 |
4 | Presenter的复用性提高 | 代码量增加,代码复杂度增加 |
5 | 便于进行测试 | |
6 | 代码灵活性增加 | |
7 | View可以进行组件化 |
四、MVP剖析
M(model)层主要职责:
- 从网络、数据库、文件、传感器、第三方等数据源进行读写数据;
- 对外部的数据类型进行解析转换为APP内部数据交由上层处理;
- 对数据的临时存储、管理、协调上层数据请求。
V(view)层主要职责:(Activity、Fragment、View、ViewGroup等)
- 提供UI交互;
- 在Presenter的控制下修改UI;
- 将业务事件交由Presenter进行处理。
P(presenter)层主要职责:
- 作为View与Model交互的中间纽带,处理与用户交互的逻辑;
- 根据用户在View中的行为去更新Model;
- 从View中获取数据然后发送给Model。
MVP中通常包含4个要素:
- View:负责绘制UI元素,与用户进行交互;
- View interface:需要View来实现的接口,View通过接口与Presenter进行交互,降低耦合,方便进行单元测试;
- Model:负责存储、检索、操纵数据;
- Presenter:作为View与Model交互的中间枢纽,处理与用户交互的逻辑。
五、一次练个够
到底是个啥?还是上代码,才能更好的理解啊!是骡子是马拉出来溜溜啊~~~~
5.1先来实现一个简单的登录功能吧!(向鸿洋大神学习下!http://blog.csdn.net/lmj623565791/article/details/46596109)
UI图.png- 要登录总要有用户吧,来个bean!
/**
* 用户类
*/
public class User {
private String userName;
private String passWord;
public String getUserName() {
return userName;
}
public void setUserName(String mUserName) {
userName = mUserName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String mPassWord) {
passWord = mPassWord;
}
}
- 有了用户,他需要做什么操作呢?实现登录功能,至少有个登录吧。
public interface IUser {
public void login(String userName,String passWord,OnLoginListener mOnLoginListener );
}
public class UserModel implements IUser {
@Override
public void login(final String userName, final String passWord, final OnLoginListener mOnLoginListener) {
//模拟子线程耗时操作
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException mE) {
mE.printStackTrace();
}
//模拟登录成功
if (userName.equals("闭关修炼") && passWord.equals("123")) {
User mUser = new User();
mUser.setUserName(userName);
mUser.setPassWord(passWord);
mOnLoginListener.loginSuccess(mUser);
} else {
mOnLoginListener.loginFailed();
}
}
}.start();
}
}
public interface OnLoginListener {
void loginSuccess(User mUser);
void loginFailed();
}
- Presenter与View的交互式通过接口实现的。所以难点就是确定View中应该有什么方法,对我这个初级菜鸟来说,简直就是难于上青天啊!
登录肯定需要有用户名和密码,那么就需要在EditText中获取吧:
//登录说明了要有用户名和密码
String getUserName();
String getPassWord();
登录的过程是个耗时操作,需要给用户一个有好的提示,一般是操作ProgressBar等待框:
void showLoading();
void hideLoading();
登录存在两种情况的处理,分别是成功后和失败后的逻辑处理:
void toMainActivity(User mUser);
void showFailedError();
还有一个重置功能(当然写在一个方法中也是可以的):
void clearUserName();
void clearPassWord();
所以View的接口应该是这样的:
/**
* View的接口
*/
public interface ILoginView {
//登录说明了要有用户名和密码
String getUserName();
String getPassWord();
//耗时操作,需要给用户一个有好的提示,一般就是操作Progress Bar
void showLoading();
void hideLoading();
//登录存在两种可能要处理的情况,登陆成功于登录失败
void toMainActivity(User mUser);
void showFailedError();
//重置功能
void clearUserName();
void clearPassWord();
}
View Interface都有了,哪个View实现就可以了啊!
public class LoginActivity extends Activity implements ILoginView {
private TextInputEditText mEtUserName;
private TextInputEditText mEtPassWord;
private Button mBtnLogin;
private Button mBtnReset;
private ProgressBar mProgressBar;
private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
//初始化
private void initView() {
mEtUserName = (TextInputEditText) findViewById(R.id.et_username);
mEtPassWord = (TextInputEditText) findViewById(R.id.et_password);
mBtnLogin = (Button) findViewById(R.id.btn_login);
mBtnReset = (Button) findViewById(R.id.btn_reset);
mProgressBar = (ProgressBar) findViewById(R.id.progressbar);
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mUserLoginPresenter.login();
}
});
mBtnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mUserLoginPresenter.clear();
}
});
}
@Override
public String getUserName() {
return mEtUserName.getText().toString();
}
@Override
public String getPassWord() {
return mEtPassWord.getText().toString();
}
@Override
public void showLoading() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mProgressBar.setVisibility(View.GONE);
}
@Override
public void toMainActivity(User mUser) {
Toast.makeText(this, "登录成功!!!", Toast.LENGTH_SHORT).show();
}
@Override
public void showFailedError() {
Toast.makeText(this, "登录失败!!!", Toast.LENGTH_SHORT).show();
}
@Override
public void clearUserName() {
mEtUserName.setText("");
}
@Override
public void clearPassWord() {
mEtPassWord.setText("");
}
}
哎,等等,这是啥! private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);
别激动,之前不就说过Presenter和View Interface进行交互嘛。现在View实现了Interface。那肯定 得有个桥梁起作用啊。
这个桥梁主要处理的就是登陆和重置的操作,要完成两者的交互,肯定需要有两者的实现类。大概就是从View中获取所需参数,交由Model进行业务处理;执行结束后的反馈和结果,在给View进行相应的显示。
/**
* Presenter是用作Model和View之间交互的桥梁
*/
public class UserLoginPresenter {
private IUser mIUser;
private ILoginView mILoginView;
private Handler mHandler = new Handler();
public UserLoginPresenter(ILoginView mILoginView) {
this.mILoginView = mILoginView;
mIUser= new UserModel();
}
public void login(){
mILoginView.showLoading();
mIUser.login(mILoginView.getUserName(), mILoginView.getPassWord(), new OnLoginListener() {
@Override
public void loginSuccess(final User mUser) {
//需要在UI线程执行
mHandler.post(new Runnable() {
@Override
public void run() {
mILoginView.toMainActivity(mUser);
mILoginView.hideLoading();
}
});
}
@Override
public void loginFailed() {
//需要在UI线程执行
mHandler.post(new Runnable() {
@Override
public void run() {
mILoginView.showFailedError();
mILoginView.hideLoading();
}
});
}
});
}
public void clear(){
mILoginView.clearUserName();
mILoginView.clearPassWord();
}
}
参考资料:
- http://blog.csdn.net/lmj623565791/article/details/46596109
- https://github.com/zhengxiaopeng/Rocko-Android-Demos/tree/master/android-mvp
- https://github.com/antoniolg/androidmvp
- https://github.com/pedrovgs/EffectiveAndroidUI
- http://zhengxiaopeng.com/2015/02/06/Android%E4%B8%AD%E7%9A%84MVP/
- http://magenic.com/Blog/Post/6/An-MVP-Pattern-for-Android
- http://antonioleiva.com/mvp-android/
- http://konmik.github.io/introduction-to-model-view-presenter-on-android.html