Android:安卓学习笔记之MVP模式的简单理解和使用
Posted JMW1407
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:安卓学习笔记之MVP模式的简单理解和使用相关的知识,希望对你有一定的参考价值。
android MVP模式的简单理解和使用
MVP模式
1、 为什么使用MVP模式?
为什么引入架构呢?引入架构的项目,必是到了一定的规模,也就是出现了一定程度的耦合与冗余,也一定意义上违反了面向对象的单一职责原则。
那么MVP解决的问题就很明显了, 那就是冗余、混乱、耦合重
。此时抛开MVP不讲,如果要我们自己想办法去解决,如何来解决呢?
分而治之
, 我们可能会想到,根据单一职责原则
,Activity或Fragment或其他组件冗余了,那么必然要根据不同的功能模块,来划分出来不同的职责模块,这样也就遵循了单一职责的原则。站在前人的智慧上,或许很多人就想到了M(Model)V(View)C(Controller)
。我们可以借鉴这一开发模式,来达到我们的目的,暂时将一个页面划分为
UI模块,也即View层
Model模块,也即数据请求模块
Logic模块, 司逻辑处理
这样划分首先职责分工就明确了,解决了混乱,冗余的问题。
- 一个项目从分包,到分类,最后拆分方法实现,都是遵从单一职责;
- 一个职责划分越具有原子性, 它的重用性就越好,当然这也要根据实际业务而定。比如以下代码:
1.1、实例说明
public class LoginActivity extends AppCompatActivity
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
btnLogin.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
final String userName = inputUserName.getText().toString();
final String password = inputPassword.getText().toString();
boolean isEmptyUserName = userName == null || userName.length() == 0;
boolean isEmptyPassword = userName == null || userName.length() == 0;
boolean isUserNameValid =Pattern.compile("^[A-Za-z0-9]3,20+$").matcher(userName ).matches();
boolean isPasswordValid = Pattern.compile("^[A-Za-z0-9]3,20+$").matcher(password ).matches();
if (isEmptyPassword || isEmptyPassword)
Toast.makeText(LoginActivity.this, "请输入帐号密码", Toast.LENGTH_SHORT).show();
else
if (isUserNameValid && isPasswordValid)
new Thread(new Runnable()
@Override
public void run()
// ...登录请求
boolean loginResult = false;
if (loginResult)
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
else
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
).start();
else
Toast.makeText(LoginActivity.this, "帐号密码格式错误", Toast.LENGTH_SHORT).show();
);
一个简单的登录, 包括点击事件
, 获取登录信息
, 判断是否空
, 校验是否正确
, 请求登录
, 返回处理
。
- 这样的代码结构混乱, 可读性差; 代码冗余,可重用性差;
- 不同功能的代码糅合在一起, 耦合性高。这只是很简单的一个小功能。
上面说到, 面向对象的单一职责原则, 一个模块划分越具有原子性,也即划分越细,那么重用性就越高。如果我改成这样
public class LoginActivity extends AppCompatActivity
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
btnLogin.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
final String userName = getEditorText(inputUserName);
final String password = getEditorText(inputPassword);
if (isEmpty(userName) || isEmpty(password))
showTips("请输入帐号密码");
else
if (isValid(userName) && isValid(password))
// 登录
doLogin(userName, password);
else
showTips("帐号密码格式错误");
);
private boolean isValid(String s)
return Pattern.compile("^[A-Za-z0-9]3,20+$").matcher(s).matches();
private boolean isEmpty(String s)
return s == null || s.length() == 0;
private String getEditorText(EditText et)
return et.getText().toString();
private void showTips(String tips)
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
private void doLogin(String username, String password)
new Thread(new Runnable()
@Override
public void run()
// ...登录请求
boolean loginResult = false;
// 更新UI
notifyLoginResult(loginResult);
).start();
private void notifyLoginResult(boolean loginResult)
if (loginResult)
showTips("登录成功");
else
showTips("登录失败");
将源码方法进行拆分后, isEmpty, isValid, showTips…等,产生的结果有亮点:
- 1) 方法拆分后,可重用性提高了
- 2) 相比而言,浏览一遍,我能基本清楚onClick里做了什么,也就是架构清晰了
这就是单一职责原则的作用,提高可重用性, 减少代码冗余,开始露出清晰的思维脉络。
以上说明了单一职责的意义,以及带来的附加的益处。那么代码经过初步重构以后, 虽然更清晰了,消除了冗余,但是耦合的问题依旧。那怎么解决耦合问题呢?我们来看下半场
2、一步步让你理解MVP
MVP最难的难点之一: 如何正确划分各模块
- Model很简单, 数据加载的界限很明确,很简单就划分出来了, 比如数据库操作, 比如文件查询, 比如网络请求,可以连带着异步操作一起拿出来,划分为单独的Model层。
View层与Presenter层交互性很频繁,很多人不清楚这一块代码算是View,还是Presenter
- 首先, 单纯的逻辑实现必然是Presenter处理的;单纯的View初始化也必然是View处理的,如findView这些。
像登录模块,View与逻辑交错在一起,怎么区分呢 ?
首先Login功能大抵分为以下子功能:
1、取值, EditText帐号与密码(明确的View层,不涉及逻辑操作)
2、判空与校验 (Presenter但涉及View, 因为使用帐号与密码,通过传参的形式)
3、登录请求 (名副其实的Model, 处理明显在Presenter层)
4、更新UI (View层)
其实以上划分界限相对比较清晰,项目中难免遇到一些不好界限的,教你一招,难以划分的必然包含View也包含逻辑处理。那么第一步,
- 原子性拆分,将View与逻辑处理单独拆分成不同的方法。View 的部分在View层, 处理的部分在Presenter层
- 有一些Toast, Dialog等的划分,根据Context作区分。
- 可以使用Application Context实现的,可以作为Presenter层; 必须使用Activity
Context的,作为View层
- 可以使用Application Context实现的,可以作为Presenter层; 必须使用Activity
那么明确了M V P的拆分,看一下拆分结果
2.1、MVP实现第一步, 将页面拆分为M/V/P三个模块
1、View 部分
public class LoginActivity extends AppCompatActivity
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
presenter.execureLogin(getEditorText(inputUserName), getEditorText(inputPassword));
);
private String getEditorText(EditText et)
return et.getText().toString();
public void showTips(String tips)
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
public void notifyLoginResult(boolean loginResult)
if (loginResult)
showTips("登录成功");
else
showTips("登录失败");
2、Model部分
public class LoginModel
private Handler handler;
public LoginModel()
handler = new Handler();
public interface OnLoginCallback
void onResponse(boolean success);
public void login(String username, String password, final OnLoginCallback callback)
new Thread(new Runnable()
@Override
public void run()
// ...请求接口
boolean result = true; // 假设这是接口返回的结果
callback.onResponse(result);
).start();
Presenter部分
public class LoginPresenter
private LoginModel model;
private LoginActivity activity;
private String verifyMsg;
public LoginPresenter(LoginActivity activity)
this.activity = activity;
model = new LoginModel();
public void execureLogin(String username, String password)
boolean verifyBefore = verifyBeforeLogin(username, password);
if (verifyBefore)
// 校验通过,请求登录
model.login(username, password, new LoginModel.OnLoginCallback()
@Override
public void onResponse(boolean success)
// 登录结果
activity.notifyLoginResult(success);
);
else
// 校验失败,提示
activity.showTips(verifyMsg);
private boolean verifyBeforeLogin(String username, String password)
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty)
verifyMsg = "请输入帐号或密码";
return false;
if (isValid)
return true;
verifyMsg = "帐号或密码错误";
return false;
private boolean isValid(String s)
return Pattern.compile("^[A-Za-z0-9]3,20+$").matcher(s).matches();
private boolean isEmpty(String s)
return s == null || s.length() == 0;
通过以上代码可以看出:
- 1、Toast提示, 更新登录状态等, 都拆分在View层;
- 2、校验与登录则拆分在Presenter层;
- 3、网络请求则拆分到了Model层。
这样每一层都只处理本层的业务,从大的方向上进行了单一职责拆分,从而整体符合单一职责原则。
根据MVP将页面拆分为了3层,单一职责的原则我们已经完全符合了。但是仔细看,忽然发现相互之间还存在依赖,解耦效果并不是那么理想。那我们要思考了,是什么原因导致耦合尚在?
那就是对象持有,看看我们的项目
- Presenter持有View(Activity)对象,同时持有Model对象
- View持有Presenter对象
MVP是怎么解决对象持有问题的?
- 面向接口编程
2.2、 MVP实现第2步, 使用接口通信,进一步解耦
对于面向对象设计来讲, 利用接口达到解耦目的已经是人尽皆知的了。 这次改动很小,把对象持有改为接口持有即可。
- View持有Presenter对象改为持有Presenter接口
- Presenter持有View对象改为持有View接口
既然持有接口,肯定要在View与Presenter分别实现供外部调用的接口。
- View供Presenter调用的方法有
notifyLoginResult
和showTips
; - Presenter供View调用的方法有
executeLogin
。
那么先来实现接口如何?看代码
Presenter接口
public interface IPresenter
/**
* 执行登录
*
* @param username
* @param password
*/
void executeLogin(String username, String password);
View接口
public interface IView
/**
* 更新登录结果
*
* @param loginResult
*/
void notifyLoginResult(boolean loginResult);
/**
* Toast提示
*
* @param tips
*/
void showTips(String tips);
接口的作用是对外部提供一种供外部调用的规范。因此这里我们把外部需要调用的方法抽象出来,加入到接口中。接口有了,且接口代表的是View或Presenter的实现,所以分别实现它们。看代码
Presenter实现接口
public class LoginPresenter implements IPresenter
private LoginModel model;
private LoginActivity activity;
private String verifyMsg;
public LoginPresenter(LoginActivity activity)
this.activity = activity;
model = new LoginModel();
@Override
public void executeLogin(String username, String password)
boolean verifyBefore = verifyBeforeLogin(username, password);
if (verifyBefore)
// 校验通过,请求登录
model.login(username, password, new LoginModel.OnLoginCallback()
@Override
public void onResponse(boolean success)
// 登录结果
activity.notifyLoginResult(success);
);
else
// 校验失败,提示
activity.showTips(verifyMsg);
private boolean verifyBeforeLogin(String username, String password)
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty)
verifyMsg = "请输入帐号或密码";
return false;
if (isValid)
return true;
verifyMsg = "帐号或密码错误";
return false;
private boolean isValid(String s)
return Pattern.compile("^[A-Za-z0-9]3,20+$").matcher(s).matches();
private boolean isEmpty(String s)
return s == null || s.length() == 0;
View实现接口
public class LoginActivity extends AppCompatActivity implements IView
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
presenter.executeLogin(getEditorText(inputUserName), getEditorText(inputPassword));
);
private String getEditorText(EditText et)
return et.getText().toString();
@Override
public void showTips(String tips)
Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
@Override
public void notifyLoginResult(boolean loginResult)
if (loginResult)
showTips("登录成功");
else
showTips("登录失败");
在接口中提供对外部调用的方法,然后分别在View和Presenter中实现它们。接口与实现都有了,还记得我们的目的是什么吗?是把持有的对象替换为接口
,撸起来,看代码
// 这是View持有的接口,在onCreate中初始化的对象由原来的LoginPresenter改为了IPresenter。
public class LoginActivity extends AppCompatActivity implements IView
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
IPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
...
Android:安卓学习笔记之MVP模式的简单理解和使用