欢迎转载,转载时请注明出处和作者
作者:kerwin
原文地址:
http://www.jianshu.com/p/284990eebf0c
****MVP->Model-View-Presenter****,意在解决业务逻辑与视觉层的解耦问题。关于MVC与MVP的区别相关资料很多,这里不再累述,相关传送门:
MVP模式(百度百科)
从上图看出,Presenter是作为Model与View层交互的连接器存在。将上图转化为代码实现的官方实例:Google官方architecture项目。
MVP模式类图
类图解析
核心的3个接口:BaseUI、BasePresenter、XContract。
****BasePresenter****,业务实现层的基础接口,所有Presenter的超父类,
方法:start(Bundle bundle),Presenter层的数据初始化,Bundle为常规页面数据传递的基本容器。
****BaseUI****,视图层的基础接口,所有UI实现类的超父类
方法:setPresenter(T presenter),用于关联UI与Presenter,主要用在Fragment与Presenter的绑定。
****XContract****,主要是为了将具体的UI和Presenter集合到一个文件中,方便集中查阅管理。
实践
需求:X页面包含3个需求。1、计算总成绩a+b,2、获取X的姓名和性别,3、展示总成绩和X的信息。
1)首先按照需求写出对应的Model类
public class XModle {
/**
* @param a a课程分数
* @param b b课程分数
* @return 总分数
*/
public int sum(int a, int b) {
return a + b;
}
/**
* 假装这里是从服务器获取的数据
* @param callback
*/
public void getX(Callback callback) {
callback.onResult("kerwin", "boy");
}
public interface Callback {
void onResult(String name, String gender);
}
}
2)创建XContract类
public interface XContract {
interface UI extends BaseUI<Presenter> {
}
interface Presenter extends BasePresenter {
}
}
3)根据业务需求添加UI和Presenter接口的方法
public interface XContract {
/**
* A课程得分的传递的参数名
*/
String PARAM_INT_A = "a";
/**
* B课程得分的传递的参数名
*/
String PARAM_INT_B = "b";
interface UI extends BaseUI<Presenter> {
/**
* 通知UI,Sum已经计算出来,可以刷新对应的UI了
*/
void refreshSumResult();
/**
* 通知UI,X的信息已经获取到,可以刷新X了。
*/
void refreshX();
/**
* 显示loading弹窗
* @param msg
*/
void showLoading(String msg);
/**
* 关闭loading弹窗
*/
void dismissLoading();
}
interface Presenter extends BasePresenter {
/**
* 获取X的姓名
* @return
*/
String getXName();
/**
* 获取X的性别
* @return
*/
String getXGender();
/**
* 获取总成绩
* @return
*/
String getSumScore();
}
}
4)实现XPresenter类
public class XPresenter implements XContract.Presenter {
private XContract.UI ui;
private XModle modle;
private int a, b;
private int sum = 0;
private String xName, xGender;
public XPresenter(XContract.UI ui) {
this.ui = ui;
this.modle = new XModle();
}
@Override
public void start(Bundle bundle) {
a = bundle.getInt(XContract.PARAM_INT_A, 0);
b = bundle.getInt(XContract.PARAM_INT_B, 0);
start();
}
private void start() {
sum = modle.sum(a, b);
ui.refreshSumResult();
ui.showLoading("正在获取X的信息");
modle.getX(new XModle.Callback() {
@Override
public void onResult(String name, String gender) {
ui.dismissLoading();
xName = name;
xGender = gender;
ui.refreshX();
}
});
}
@Override
public String getXName() {
return "姓名:"+xName;
}
@Override
public String getXGender() {
return "性别:"+xGender;
}
@Override
public String getSumScore() {
return "总成绩"+sum;
}
}
到这里,可以看到,我们Activity还没有开始写,但是我们的业务流程已经完成了,这就是MVP的魅力。至于页面长什么样子并不需要业务逻辑层去关注。现在我们理一下整个需求完成的程序流图。
****程序流图****
看过程序流图后结合之前的代码,可能会问:为什么UI里面没有setXName方法,为什么Presenter里面有getXName方法。为什么Presenter从XModel里面获取到Sum返回值后不是直接调用ui.setSum(sum),而是ui.refreshSumResult()?
个人实践中发现,UI层是随时可能会变化的,但是Presenter基本不变,因为接口基本不变。所以,我推荐的MVP实践方式是,主动的在UI中从Presenter获取数据,而不是在Presenter中主动的修改UI信息,Presenter完成从Model获取数据的职责后仅需通知UI已经完成数据获取,UI想如何显示或显示什么自己决定。
****下面继续完善UI部分的实现代码****
5)新增layout文件activity_x.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/tv_sum"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_x_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/tv_x_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" />
</LinearLayout>
6)新增XActivity类
public class XActivity extends Activity implements XContract.UI {
private TextView tv_x_name;
private TextView tv_x_gender;
private TextView tv_sum;
private XContract.Presenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_x);
initView();
initPresenter();
}
private void initView() {
tv_sum = (TextView) findViewById(R.id.tv_sum);
tv_x_name = (TextView) findViewById(R.id.tv_x_name);
tv_x_gender = (TextView) findViewById(R.id.tv_x_gender);
}
private void initPresenter() {
presenter = new XPresenter(this);
presenter.start(getIntent().getExtras());
}
@Override
public void setPresenter(XContract.Presenter presenter) {
// 如果UI实现是一个Fragment,这里的代码是需要的,是Activity的这里留空即可
this.presenter = presenter;
}
@Override
public void refreshSumResult() {
tv_sum.setText(presenter.getSumScore() + "");
}
@Override
public void refreshX() {
tv_x_name.setText(presenter.getXName());
tv_x_gender.setText(presenter.getXGender());
}
@Override
public void showLoading(String msg) {
//显示Dialog弹窗
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void dismissLoading() {
//关闭Dialog弹窗
}
}
至此,所有的需求全部完成了。
****说在最后****
MVP每一层都有自己的职责,请尽量做到各司其职。
几点实践建议:
1、View层不包含任何Model层的代码以及引用
2、Presenter作为连接器,尽量不要直接调用UI层的具体UI更新的方法,仅需通知UI层自己去刷新某一个细分模块(就像上面的refreshX)