MVP设计模式从提出至今也有不短的时间了,大家应该或多或少使用过MVP模式开发项目,或者至少听说过MVP设计模式,不同的人对其有不同的理解,今天就来说说我所理解的MVP设计模式。
MVC
说起MVP就不得不提MVC设计模式,MVP模式是从MVC模式中演化出来的。MVC包含以下三种组件:
- 控制器(Controller)- 负责转发请求,对请求进行处理。
- 视图(View) - 界面设计人员进行图形界面设计。
- 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
他们之间的联系为:
View负责显示图形界面与获取用户事件输入,事件输入之后交给Controller进行逻辑控制处理,Controller调用Model进行数据处理(如:操作数据库读取数据等),当Model中数据发生改变后会通知Controller,Controller控制View刷新界面。同时View也可以直接将数据交给Model处理,并监听Model中的数据改变来刷新界面。(详细介绍可参考维基百科中的MVC设计模式)
MVP
MVP设计模式同样包含三种组件:
- Model 定义用户界面所需要被显示的数据模型,一个模型包含着相关的业务逻辑。
- View 视图为呈现用户界面的终端,用以表现来自 Model 的数据,和用户命令路由再经过 Presenter 对事件处理后的数据。
- Presenter 包含着组件的事件处理,负责检索 Model 获取数据,和将获取的数据经过格式转换与 View 进行沟通。
但他与MVC设计模式不同的是,MVP模式实现了View与Model的完全解耦。结构图如下:
可以看到相比MVC模式MVP各组件之间的分工更明确,View只负责UI展示和用户事件输入,Presenter负责协调View和Model的沟通,Model负责数据操作,数据操作的结果只需要反馈给Presenter。
这样设计的优点也显而易见:
- 分类了视图、逻辑、数据层,降低了个模块之间的耦合性,并实现了视图层和数据层的完全解耦。
- 个组件之间通过接口实现交互,可以很方便的进行单元测试。
- 利于代码的复用,不同的Activity可以复用同一个Presenter,同样的不同Presenter也可以复用同一个Model进行数据处理。
- 代码更加灵活
- 对于大项目来说,方便不同开发人员进行模块化开发协作。
代码实现
上面说了这么多还得最终落实到代码上,下面将通过MVP模式实现简单的登录功能。
UML类图(不太熟练,如有错误,望不吝赐教):
项目结构:
分别创建View、Presenter、Model三个包存放三种组件的实现类。
UserBean:
用户存放用户信息的实体类,添加一个变量用于模拟不同的登录状态。
public class UserBean {
private String userName;
private String password;
//模拟不同的登录状态
private String loginResultType = "1";
private String token;
public UserBean() {
}
public UserBean(String userName, String password) {
this.userName = userName;
this.password = password;
}
public UserBean(String userName, String password, String loginResultType, String token) {
this.userName = userName;
this.password = password;
this.loginResultType = loginResultType;
this.token = token;
}
//Getter和Setter代码不贴了
@Override
public String toString() {
return "UserName=" + userName
+ "\n Password=" + password
+ "\n token=" + token;
}
}
View:
ILoginView接口定义:
public interface ILoginView {
void showLoading();
void hideLoading();
/**
* 登录成功
* @param userBean 用户类
*/
void showLoginSuccess(UserBean userBean);
/**
* 显示登录失败信息
* @param message 失败信息
*/
void showFailureMessage(String message);
/**
* 显示登录错误信息
* @param message 错误信息
*/
void showErrorMessage(String message);
}
LoginActivity实现:
public class LoginActivity extends AppCompatActivity implements ILoginView{
private static final String TAG = LoginActivity.class.getSimpleName();
private ILoginPresenter loginPresenter; //login Presenter
private RadioGroup loginResultRg; //模拟登录状态的RadioGroup
private EditText userNameEt; //用户名
private EditText passwordEt; //密码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loginResultRg = findViewById(R.id.login_result_rg);
userNameEt = findViewById(R.id.user_name_et);
passwordEt = findViewById(R.id.password_et);
loginPresenter = new LoginPresenter(this);
}
/**
* 登录事件
* @param view 事件触发View
*/
public void login(View view) {
UserBean userBean = new UserBean();
userBean.setUserName(userNameEt.getText().toString().trim());
userBean.setPassword(passwordEt.getText().toString().trim());
//通过RadioButton的选中状态模拟不同的登录状态
switch (loginResultRg.getCheckedRadioButtonId()){
case R.id.success_rb:
userBean.setLoginResultType("1");
break;
case R.id.failure_rb:
userBean.setLoginResultType("2");
break;
case R.id.error_rb:
userBean.setLoginResultType("3");
break;
}
loginPresenter.getLoginData(userBean);
}
@Override
public void showLoading() {
Log.d(TAG, "showLoading");
Toast.makeText(LoginActivity.this, "showLoading", Toast.LENGTH_SHORT).show();
}
@Override
public void hideLoading() {
Log.d(TAG, "hideLoading");
Toast.makeText(LoginActivity.this, "hideLoading", Toast.LENGTH_SHORT).show();
}
@Override
public void showLoginSuccess(UserBean userBean) {
Log.d(TAG, "showLoginSuccess user Information " + userBean.toString());
Toast.makeText(LoginActivity.this, "showLoginSuccess userName=" + userBean.toString(), Toast.LENGTH_SHORT).show();
}
@Override
public void showFailureMessage(String message) {
Log.d(TAG, "showFailureMessage message= " + message);
Toast.makeText(LoginActivity.this, "showFailureMessage msg=" + message, Toast.LENGTH_SHORT).show();
}
@Override
public void showErrorMessage(String message) {
Log.d(TAG, "showErrorMessage message=" + message);
Toast.makeText(LoginActivity.this, "showErrorMessage msg=" + message, Toast.LENGTH_SHORT).show();
}
}
XML就不贴了需要的可以去下载demo看。
Model:
ILoginModel接口定义:
public interface ILoginModel {
/**
* 登录操作
* @param param 参数
*/
void doLogin(UserBean param, LoginCallBack loginCallBack);
/**
* 登录状态回调
*/
public interface LoginCallBack{
/**
* 登录成功
* @param data 返回数据
*/
void onSuccess(UserBean data);
/**
* 调用登录接口时,接口调用成功,但是
* 因用户名错误、登录失效等后台控制逻辑导致的登录失败
* @param data 失败原因
*/
void onFailure(String data);
/**
* 接口调用失败
* 网络不通
* 接口超时
* 404、500等原因
* @param error 失败原因
*/
void onError(String error);
/**
* 接口请求结束,包括上面三中情况
* 设置此方法通常是进行hideLoading等操作
*/
void onComplete();
}
}
LoginModel:
public class LoginModel implements ILoginModel {
private Handler handler;
public LoginModel(){
handler = new Handler(Looper.getMainLooper());
}
@Override
public void doLogin(final UserBean param, final LoginCallBack loginCallBack) {
loginCallBack.onComplete();
//模拟登录延迟操作
handler.postDelayed(new Runnable() {
@Override
public void run() {
switch (param.getLoginResultType()){
case "1":
param.setToken("登录成功");
loginCallBack.onSuccess(param);
break;
case "2":
loginCallBack.onFailure("用户名或密码错误");
break;
case "3":
loginCallBack.onError("接口超时");
break;
}
}
}, 3000);
}
}
Presenter:
ILoginPresenter接口定义:
public interface ILoginPresenter {
/**
* 获取登录数据
* @param param 参数
*/
void getLoginData(UserBean param);
}
LoginPresenter实现:
public class LoginPresenter implements ILoginPresenter {
private ILoginView loginView;
private ILoginModel loginModel;
public LoginPresenter(ILoginView loginView){
this.loginView = loginView;
loginModel = new LoginModel();
}
@Override
public void getLoginData(UserBean userBean) {
loginView.showLoading();
loginModel.doLogin(userBean, new ILoginModel.LoginCallBack() {
@Override
public void onSuccess(UserBean data) {
loginView.showLoginSuccess(data);
}
@Override
public void onFailure(String data) {
loginView.showFailureMessage(data);
}
@Override
public void onError(String error) {
loginView.showErrorMessage(error);
}
@Override
public void onComplete() {
loginView.hideLoading();
}
});
}
}
一句话总结一下登录流程:
用户点击登录按钮触发登录操作,View也就是LoginActivity调用Presenter的getLoginData()方法,开启登录逻辑,Presenter调用Model的doLogin方法,执行具体的登录操作。Model将登录结果通过回调反馈给Presenter,Presenter控制View进行相应的UI显示。
另一种实现:
上面这种实现是最基本的实现,下面介绍另一种实现,将IView、IModel、IPresenter中的接口封装到contract中,并实现相关的基类方便其他模块扩展实现MVP模式。
UML类图:
项目结构
Contract类
添加了Contract包,用于存放不同模块的协约类,用于将上一种实现方式中分散在IView、IModel、IPresenter中的接口统一归纳、统一管理。
public class LoginContract {
/**
* 登录View接口
*/
public interface ILoginView {
void showLoading();
void hideLoading();
/**
* 登录成功
* @param userBean 用户类
*/
void showLoginSuccess(UserBean userBean);
/**
* 显示登录失败信息
* @param message 失败信息
*/
void showFailureMessage(String message);
/**
* 显示登录错误信息
* @param message 错误信息
*/
void showErrorMessage(String message);
}
/**
* 登录Presenter
*/
public interface ILoginPresenter {
/**
* 获取登录数据
* @param param 参数
*/
void getLoginData(UserBean param);
}
/**
* 登录Model
*/
public interface ILoginModel {
/**
* 登录操作
* @param param 参数
*/
void doLogin(UserBean param, LoginCallBack loginCallBack);
}
}
View
IView:
定义了一个IView接口类,此类中抽象出所有View共同的方法,如:showLoading、hideLoading等,还有个作用就是为所有的View定义统一的接口方便之后在BasePresenter中进行泛型。
public interface IView {
//定义统一的空接口
}
BaseActivity:
定义基类BaseActivity,封装一些通用方法便于其他模块的Activity进行扩展。注意在此类中实现了IView接口,所以在之后的Activity中不在需要实现IView接口。
public abstract class BaseActivity extends AppCompatActivity implements IView{
//定义Presenter的泛型进行约束
protected P mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(initLayout() instanceof Integer){
setContentView((Integer) initLayout());
} else if(initLayout() instanceof View){
setContentView((View) initLayout());
} else{
throw new IllegalArgumentException("initLayout() 应该返回Int或者View类型对象");
}
//初始化Presenter
mPresenter = initPresenter();
//Presenter与View进行绑定
mPresenter.attachView(this);
create();
}
@Override
protected void onDestroy() {
super.onDestroy();
//Presenter与View解除绑定
mPresenter.detachView();
}
/** 初始化Presenter的抽象方法 */
protected abstract P initPresenter();
/** 初始化布局的抽象方法 */
protected abstract Object initLayout();
/** Activity OnCreate之后的create抽象方法 */
protected abstract void create();
}
LoginActivity:
LoginActivity需要继承其父类BaseActivity并实现Login协约类中的View接口。
ublic class LoginActivity extends BaseActivity implements LoginContract.ILoginView {
private static final String TAG = LoginActivity.class.getSimpleName();
private RadioGroup loginResultRg; //模拟登录状态的RadioGroup
private EditText userNameEt; //用户名
private EditText passwordEt; //密码
@Override
protected LoginPresenter initPresenter() {
return new LoginPresenter();
}
@Override
protected Object initLayout() {
return R.layout.activity_main;
}
@Override
protected void create() {
loginResultRg = findViewById(R.id.login_result_rg);
userNameEt = findViewById(R.id.user_name_et);
passwordEt = findViewById(R.id.password_et);
}
/**
* 登录事件
* @param view 事件触发View
*/
public void login(View view) {
UserBean userBean = new UserBean();
userBean.setUserName(userNameEt.getText().toString().trim());
userBean.setPassword(passwordEt.getText().toString().trim());
//通过RadioButton的选中状态模拟不同的登录状态
switch (loginResultRg.getCheckedRadioButtonId()){
case R.id.success_rb:
userBean.setLoginResultType("1");
break;
case R.id.failure_rb:
userBean.setLoginResultType("2");
break;
case R.id.error_rb:
userBean.setLoginResultType("3");
break;
}
mPresenter.getLoginData(userBean);
}
@Override
public void showLoading() {
Log.d(TAG, "showLoading");
Toast.makeText(LoginActivity.this, "showLoading", Toast.LENGTH_SHORT).show();
}
@Override
public void hideLoading() {
Log.d(TAG, "hideLoading");
Toast.makeText(LoginActivity.this, "hideLoading", Toast.LENGTH_SHORT).show();
}
@Override
public void showLoginSuccess(UserBean userBean) {
Log.d(TAG, "showLoginSuccess user Information " + userBean.toString());
Toast.makeText(LoginActivity.this, "showLoginSuccess userName=" + userBean.toString(), Toast.LENGTH_SHORT).show();
}
@Override
public void showFailureMessage(String message) {
Log.d(TAG, "showFailureMessage message= " + message);
Toast.makeText(LoginActivity.this, "showFailureMessage msg=" + message, Toast.LENGTH_SHORT).show();
}
@Override
public void showErrorMessage(String message) {
Log.d(TAG, "showErrorMessage message=" + message);
Toast.makeText(LoginActivity.this, "showErrorMessage msg=" + message, Toast.LENGTH_SHORT).show();
}
}
Model
IModel:
统一的接口类IModel,作用同上IView接口。
public interface IModel {
}
LoginModel:
实现IModel和LoginContract.ILoginModel接口
public class LoginModel implements IModel,LoginContract.ILoginModel {
private Handler handler;
public LoginModel(){
handler = new Handler(Looper.getMainLooper());
}
@Override
public void doLogin(final UserBean param, final LoginCallBack loginCallBack) {
loginCallBack.onComplete();
//模拟登录延迟操作
handler.postDelayed(new Runnable() {
@Override
public void run() {
switch (param.getLoginResultType()){
case "1":
param.setToken("登录成功");
loginCallBack.onSuccess(param);
break;
case "2":
loginCallBack.onFailure("用户名或密码错误");
break;
case "3":
loginCallBack.onError("接口超时");
break;
}
}
}, 3000);
}
}
Presenter
IPresenter:
统一的Presenter接口,定义了绑定和解绑View的方法。
public interface IPresenter {
void attachView(IView view);
void detachView();
}
BasePresenter:
定义View与Model的泛型进行约束,实现上面的接口。
public abstract class BasePresenter implements IPresenter{
protected V mView;
protected M mModel;
public BasePresenter(){
mModel = initModel();
}
@Override
public void attachView(IView view) {
mView = (V) view;
}
/**
*初始化Moel的抽象方法
*/
protected abstract M initModel();
@Override
public void detachView() {
mView = null;
mModel = null;
}
}
LoginPresenter:
继承基类,实现接口,没什么好说的。
public class LoginPresenter extends BasePresenter implements LoginContract.ILoginPresenter {
@Override
public void getLoginData(UserBean userBean) {
mView.showLoading();
mModel.doLogin(userBean, new LoginContract.ILoginModel.LoginCallBack() {
@Override
public void onSuccess(UserBean data) {
mView.showLoginSuccess(data);
}
@Override
public void onFailure(String data) {
mView.showFailureMessage(data);
}
@Override
public void onError(String error) {
mView.showErrorMessage(error);
}
@Override
public void onComplete() {
mView.hideLoading();
}
});
}
@Override
protected LoginModel initModel() {
return new LoginModel();
}
}
至此关于MVP就介绍完了,并扩展了一种MVP的实现方式,实现方式并不是固定的,你可以根据自己对MVP的理解和项目需要自行实现MVP设计模式。
Demo:博客中的项目Demo
[1]文中引用部分均来自中文维基百科]1