转载请标明出处:
http://blog.youkuaiyun.com/checkiming/article/details/71909629;
本文出自:【checkiming的博客】
1. 前言
目前很多Android开发程序员们在一个安卓应用整体项目代码构架中都会选择MVP模式,或许你可以随口说出降低耦合度,实现Model和View真正分离,可以修改View而不影响Model , 模块职责划分明显,层次清晰 , 隐藏数据等等mvp构架的优势,但它真的是完全没有任何缺陷吗? 俗话说,任何一项事物面前都有利弊的一面,同样MVP构架模式也有,这里会利用一个loginDemo形象的说明MVP在构造中的优势存在.
阅读提示:下文首先会先分析各个构架模式之间的区别(主要说明MVP),然后进行阐述MVP的优劣方面,最后会利用MVP模式实现一个登录界面的优化过程
2. MVC,MVP,MVVM的大概区别
MVC
MVC软件可以分为三部分
- 视图(View):用户界面
- 控制器(Controller):业务逻辑
- 模型(Model):数据保存
各部分之间的通信方式如下:
- View传送指令到Controller
- Controller完成业务逻辑后,要求Model改变状态
- Model将新的数据发送到View,用户得到反馈
- Tips:所有的通信都是单向的。
互动模式 接受用户指令时,MVC可以分为两种方式。一种是通过View接受指令,传递给Controller。另一种是直接通过Controller接受指令
MVP
MVP模式将Controller改名为Presenter,同时改变了通信方向。
各部分之间的通信,都是双向的
View和Model不发生联系,都通过Presenter传递
View非常薄,不部署任何业务逻辑,称为”被动视图”(Passive View),即没有任何主动性,而Presenter非常厚,所有逻辑都部署在那里。
MVVM
MVVM模式将Presenter改名为ViewModel,基本上与MVP模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在ViewModel,反之亦然。
3. 为什么需要用到MVP?
- 尽量简单 大部分的安卓应用只使用View-Model结构,程序员现在更多的是和复杂的View打交道而不是解决业务逻辑。当你在应用中只使用Model-View时,到最后,你会发现“所有的事物都被连接到一起”。复杂的任务被分成细小的任务,并且很容易解决。越小的东西,bug越少,越容易debug,更好测试。在MVP模式下的View层将会变得简单,所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。
- 后台任务 当你编写一个Actviity、Fragment、自定义View的时候,你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样,你的Task不再和Activity联系在一起,这既不会导致内存泄露,也不依赖于Activity的重建。
优点
- 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle
- 模块职责划分明显,层次清晰
- 隐藏数据
- Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下)
- 利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。
- View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。
- 代码灵活性
缺点
- Presenter中除了应用逻辑以外,还有大量的View->Model,Model-View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
- 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。
- 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。
- 额外的代码复杂度及学习成本。
概念解释: MVP是Model(数据),View(界面),Presenter(变现层) 的缩写,它是MVC 构架的变种,强调Model和View的最大化解耦和单一职责原则
在MVP模式里通常包含4个要素:
1. Model :负责存储、检索、操纵数据(数据的来源和封装),比如网络请求类,数据库操作类以及java bean,如果有必要则提供接口暴露自己处理数据的状态和进度.(有时也实现一个Model interface用来降低耦合);
2. View :负责UI相关,如布局UI的初始化,各种listener的设置。在Android中,我们通常写的Activity和Fragment就是属于View层;在web开发中,html则是View层。
3. View interface :需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;
4. Controller(控制器): 业务逻辑控制器,主要负责当获取到数据后对数据进行逻辑处理,然后将数据绑定到View上;比如:请求一个url,从网络获取到数据,进行解析javabean,然后各种set数据。对于控制器的概念大家很好理解,因为我们每天都在这样做,在Activity中请求数据然后更新UI。但是结合View的概念来看,很显然Activity和Fragment不但承担了View的任务,还负责完成的Controller的功能,随着业务功能的增多,Activity的代码越来越难以阅读和维护,这就是在Android中使用MVC的弊端,为了解决MVC模式下View层的臃肿,MVP模式应运而生。
5. Presenter :作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。同样我们也可以这样理解,它是专门从Controller 中独立出来的业务逻辑层,主要负责处理原先View 层的业务逻辑,解决了Activity的臃肿问题,让Activity只负责处理UI,职责更加明确; 并且将View 层的业务逻辑抽取到Presenter之后,View层与Model 层也实现了解耦;便于后期代码的扩展和维护,并且业务逻辑层独立后代码还得到了很大的重用性.
总结:MVC模式下,View和Controller纠缠不清,并且View和Model相互关联,而MVP模式下Model和View解耦,便于单元测试,项目维护,代码重用.
4. 利用MVP模式实现登录界面
项目结构:
1. 先看看一个登录界面的业务逻辑和UI更新全部写在Activity的代码
public class LoginActivity extends ActionBarActivity implements OnClickListener{
EditText etUsername,etPassword;
Button btn_login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etUsername = (EditText) findViewById(R.id.et_username);
etPassword = (EditText) findViewById(R.id.et_password);
btn_login = (Button) findViewById(R.id.btn_login);
//设置点击事件
btn_login.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
if(checkInput(username,password)){
//提交登录
//执行登录请求
execLogin(username, password);
}
break;
}
}
//执行登录请求
public void execLogin(String username,String password){
//执行登录请求的伪代码
HttpHelper helper = new HttpHelper();
helper.execRequest("http://www.baidu.com", new HttpCallback() {
@Override
public void onSuccess() {
//提示登录成功
showLoginSuccess(LoginActivity.this);
//保存登录相关数据,如登录的标记,用户的唯一标识
saveLoginData();
}
@Override
public void onFail() {
//关闭登录对话框
hideLoginDialog(LoginActivity.this);
//提示登录失败
showLoginFail(LoginActivity.this);
}
});
}
//保存登录数据
public void saveLoginData(){
//保存登录相关的数据,代码略过...
}
//提示登录成功
public void showLoginFail(Context context){
Toast.makeText(context, "登录失败", 0).show();
}
//提示登录失败
public void showLoginSuccess(Context context){
Toast.makeText(context, "登录成功", 0).show();
}
//检查输入的合法性
private boolean checkInput(String username, String password) {
boolean result = true;
//1.检查为空
if(TextUtils.isEmpty(username) || TextUtils.isEmpty(password)){
Toast.makeText(this, "用户名或者密码不能为空!", 0).show();
result = false;
}
//2.检查长度
if(username.length()!=11){
Toast.makeText(this, "用户名长度不正确!", 0).show();
result = false;
}
if(password.length()<5){
Toast.makeText(this, "密码长度不能小于5位!", 0).show();
result = false;
}
return result;
}
}
2.现在抽取出LoginPresenter类,如下:
/**
* 业务逻辑封装层
* @author checkiming
*
*/
public class LoginPresenter {
private ILoginView loginView;
/**
* 生命周期相关方法
*/
public void onDestory(){
//do something to release and avoid memory leak;
}
public void onStart(){
//do something when onStart
}
public void onStop(){
//do something when onStop
}
public void onResume(){
//do something when onResume
}
public void onPause(){
//do something when onPause
}
public LoginPresenter(ILoginView loginView){
this.loginView = loginView;
}
//登录的方法
public void login(String username, String password) {
if(!checkInput(username, password)){
return;
}
// 执行登录请求的伪代码
HttpHelper helper = new HttpHelper();
helper.execRequest("http://www.baidu.com", new HttpCallback() {
@Override
public void onSuccess() {
// 需要UI展示,暴露接口
loginView.showLoginSuccess();
// 保存登录相关数据,如登录的标记,用户的唯一标识
saveLoginData();
}
@Override
public void onFail() {
// 需要UI展示,暴露接口
loginView.showLoginFail();
}
});
}
private void saveLoginData() {
//do something
}
// 检查输入的合法性
private boolean checkInput(String username, String password) {
boolean result = true;
// 1.检查为空
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
loginView.showInputNoNull();
result = false;
}
// 2.检查长度
if (username.length() != 11) {
loginView.showUsernameLengthError();
result = false;
}
return result;
}
}
3.View和P解耦接口如下ILoginView:
public interface ILoginView {
void showLoginSuccess();
void showLoginFail();
void showInputNoNull();
void showUsernameLengthError();
}
4.最后的View层编写如下:
public class LoginActivity extends Activity implements OnClickListener,ILoginView{
EditText etUsername,etPassword;
Button btn_login;
private LoginPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etUsername = (EditText) findViewById(R.id.et_username);
etPassword = (EditText) findViewById(R.id.et_password);
btn_login = (Button) findViewById(R.id.btn_login);
//设置点击事件
btn_login.setOnClickListener(this);
//与loginPresenter交互
loginPresenter = new LoginPresenter(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
loginPresenter.login(username,password);
break;
}
}
public void showLoginSuccess(){
Toast.makeText(this, "登录成功", 0).show();
}
public void showLoginFail(){
Toast.makeText(this, "登录失败", 0).show();
}
public void showInputNoNull(){
Toast.makeText(this, "用户名和密码不能为空!", 0).show();
}
public void showUsernameLengthError(){
Toast.makeText(this, "用户名长度不正确!", 0).show();
}
public void hideLoginDialog(){
if(progressDialog!=null){
progressDialog.dismiss();
}
}
@Override
protected void onResume() {
super.onResume();
loginPresenter.onResume();
}
@Override
protected void onStart() {
super.onStart();
loginPresenter.onStart();
}
@Override
protected void onStop() {
super.onStop();
loginPresenter.onStop();
}
@Override
protected void onPause() {
super.onPause();
loginPresenter.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter.onDestory();
}
}
总结:MVP只是给我们提出了分层解耦的思想,并没有一个固定的实现。Google虽然出了官方的MVP实现示例,但是并没有太多人去跟随,很多公司在对Presenter层都有自己的理解,这里呢只是属于我自己的理解,此处的案例相对来说比较规范,对MVP三层都有清晰的解耦和实现,在茫茫开源项目中,算是比较好的容易理解的上手项目了。