优化你的代码结构 --- MVP
安卓基础开发库,让开发简单点。
DevRing & Demo地址:https://github.com/LJYcoder/DevRing
学习/参考地址:
https://www.jianshu.com/p/91c2bb8e6369
http://www.jianshu.com/p/9d40b298eca9
http://blog.csdn.net/lmj623565791/article/details/46596109
MVP是什么?
Most Valuable Player(最有价值运动球员)? 不不不,虽然我很喜欢看nba,但此MVP非彼MVP。
这里的MVP是指安卓中的一种开发模式。
它将代码整体分为M(Model)、V(View)、P(Presenter)三层。
正经版:
M层(model):数据模型/处理层。负责数据处理、数据提供,如网络请求,数据库操作
V层(view):视图展示层。负责界面展示,如Activity,Fragment
P层(presenter):业务逻辑层。负责业务逻辑服务,是V层与M层间的桥梁
你也可以这样帮助理解下(餐厅版):
M层(model):厨师。负责做菜
V层(view):顾客。点餐吃饭
P层(presenter):服务员。提供下单、上菜等各种服务
与MVC模式的区别:
MVC中,V层与M层是可以互通的,而在MVP中V层与M层是不通的。
按餐厅版来说就是,MVC中顾客可以直接告诉厨师要吃什么菜,厨师做好后直接把菜端到你面前,而MVP中只能通过服务员来完成点餐到用餐的过程。
使用MVP有什么好处?
抽象些来说:
MVP可以降低代码耦合度,提高代码的结构清晰度、可读性、维护性和复用性。
具体些来说(参考JessYan的例子):
现在有这么一个需求:Activity中从网络获取数据然后展示在A控件上。
如果不用MVP的话,那就直接把获取展示等代码都写在Activity中,很快便可以写完。
但现在需求变动了:
1.要求加入缓存功能,如果本地有数据,则先从本地获取数据,然后再从网络获取最新数据进行替换
2.要求数据展示在B控件上而不是A控件。
如果代码都是你自己写的,那改起来还比较轻松,但假如是团队开发,代码不是你写的,你需要花时间把逻辑重新看一遍再开始改,而且如果改错的话,会影响之前已经写好的功能。
但使用MVP模式进行开发就不同了。由于它的分工结构清晰,V层仅负责数据展示,P层仅负责业务逻辑,M层仅负责数据获取/处理。所以改动起来就轻松很多。
对于变动的需求1:我们只需在P层加入逻辑判断(先从本地获取,再网络获取),然后M层增加一个从本地获取数据方法。
对于变动的需求2:我们只需在V层修改获取到数据后的展示方式,从控件A改成控件B。
当然还有可复用等优点,这里就不具体讲了。
至于"缺点"嘛,就是会相应地增加代码量。有得有失,但得大于失。
具体使用
以这么一个场景为例:
从网络获取“正在上映”的电影数据,获取成功则将数据在页面展示,获取失败则给出相应提示。
需要写四个部分:Model层,View层,Presenter层,接口
接口
负责“连接”MVP三层,以便方法调用、数据流动。同时也便于进行单元测试。
IView
View层接口,定义View层需实现的方法,P层通过该接口回调通知View层。
public interface IMovieView {
//成功获取到电影数据
void getMovieSuccess(List<MovieRes> list, int type);
//获取电影数据失败
void getMovieFail(int status, String desc, int type);
}
IModel
Model层接口,定义Model层需实现的方法,P层通过该接口调用M层获取/处理数据的方法。
public interface IMovieMoel{
//请求正在上映的电影数据
Observable getPlayingMovie(int start, int count);
...
}
Model层
实现IModel接口中的方法,负责数据的获取/处理。
public class MovieModel implements IMovieMoel{
@Override
public Observable getPlayingMovie(int start,int count) {
//提供数据源
return DevRing.httpManager().getService(MovieApiService.class).getPlayingMovie(start, count);
}
...
}
Presenter层
处理业务逻辑,调用M层获取数据,调用V层传递展示数据。
public class MoviePresenter {
private IMovieView mIView;
private IMovieModel mIModel;
public MoviePresenter(IMovieView iMovieView, IMovieMoel iMovieMoel) {
mIView = iMovieView;
mIModel = iMovieModel;
}
/**
* 获取正在上映的电影
*
* @param start 请求电影的起始位置
* @param count 获取的电影数量
* @param type 类型:初始化数据INIT、刷新数据REFRESH、加载更多数据LOADMORE
*/
public void getPlayingMovie(int start, int count, final int type) {
DevRing.httpManager().commonRequest( mIModel.getPlayingMovie(start, count),
new CommonObserver<HttpResult<List<MovieRes>>>() {
@Override
public void onResult(HttpResult<List<MovieRes>> result) {
if (mIView != null) {
mIView.getMovieSuccess(result.getSubjects(), type);
}
}
@Override
public void onError(int errType, String errMessage) {
if (mIView != null) {
mIView.getMovieFail(errType, errMessage, type);
}
}
}, RxLifecycleUtil.bindUntilDestroy(mIView));
}
...
/**
* 释放引用,防止内存泄露
*/
public void destroy() {
mIView = null;
}
}
View层
实现IView接口中的方法,对获取到的数据进行展示
public class MovieActivity extends Activity implements IMovieView {
//获取电影数据成功的网络请求回调
@Override
public void getMovieSuccess(List<MovieRes> list, int type) {
//成功,对数据进行展示
....
}
//获取电影数据失败的网络请求回调
@Override
public void getMovieFail(int status, String desc, int type) {
//失败,界面上做出相应提示
...
}
...
}
完成以上几步后,在View层初始化时,调用Presenter层方法即可。
@Override
protected void onCreate(Bundle saveInstanceState) {
...
mPresenter = new MoviePresenter(this, new MovieModel());
mPresenter.getPlayingMovie(start, mCount, type);
}
还有一点需注意:
如果Presenter层持有了View层的引用,那么记得在V层销毁时,把Presenter层中对View层的引用置null,避免View层回收失败导致内存泄漏。
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.destroy();
mPresenter = null;
}
}
2018.4.13:
github的DevRing项目中新增了MVP一键生成模板,根据Demo的代码结构定制的,有需要的可以查看。