关于 MVP 模式的几点思考


最近在完善应用 有干货 的过程中,被 MVP 模式困扰住了,主要是关于 Context 对象的。不过最后还是想通了,并说服了自己。下面记录一下困扰我的几个问题和解答,如果你发现我有理解不对的地方,还希望能指出来,这里先感谢了。

Presenter 该不该 持有 Context 对象

我的答案是不该持有。

Presenter 具有脱离 Context 的可测试性,Presenter 应该是和 Context 无关的,并且不依赖 Android API 方法,这样能确保不依赖 Android 设备进行单元测试。

如果真的需要 Context 对象,怎么办?比如 DataCleanManager 工具类,是属于 Model 层的,要在 Presenter 中调用,而不是 View 中,但是它需要 Context 来进行数据的清理,一开始,我在 Presenter 中获取 Activity 的 Context 对象,再传递给 DataCleanManager,后来发现这样做很不好,导致了Presenter 和 View 的耦合,其实可以使用 Appplication 的 Context 来做相同的事,我们知道 Appplication 的 Context 和 Activity 的 Context 的一个区别是 Activity 的 Context 使用在和界面相关的操作上,比如显示一个 Dialog,而 Application 的 Context 则不行,但是其他的 Context 相关的操作,都可以使用 Application 的 Context 来做,所以,这个工具类在清理数据时,直接自己获取 Application 的 Context 来完成,而不依赖 Activity,也就和 View 层解除了耦合,这样在测试 Presenter 时,可以完全把它当作普通的 Java 类来测试。并且其他操作 View 的类,可以直接在 View 中使用,而不需要通过 Presenter。

在 Presenter 中使用 Context 对象时,先问问自己这个 Context 对象用来干嘛的,和 View 界面操作相关的就不要在 Presenter 使用,而是直接在 View 层操作,和界面无关的话,就在 Model 层使用 Application 的 Context。

Intent 该不该交给 Presenter

Intent 获取数据

上一个界面传递过来的数据,是直接把 Intent 交给 Presenter,还是由 View 处理后把数据给 Presenter 呢?

看以下两种方式:

1.直接把 Intent 交给 Presenter

View:

@Override
public Intent getIntent(){
    return this.getIntent();
}

Presenter:

Intent intent = mView.getIntent();
String url = intent.getString(BigimgActivity.MEIZI_TITLE);
// 后续操作

2.View 处理后把数据给 Presenter

View:

@Override
public String getUrl(){
    return this.getIntent().getString(BigimgActivity.MEIZI_TITLE);
}

Presenter:

String url = mView.getUrl();
// 后续操作

第一种方式 Presenter 操作了负责界面跳转并携带数据的 Intent 对象,导致 Presenter 职责不明,并且测试 Presenter 时也依赖了 Intent。第二种方式避免了这个问题。

Intent 跳转界面传递数据

跳转到下一个界面,是全权交给 Presenter,还是分离出跳转和获取数据分别交给 View 和 Presenter 操作呢?

看以下两种方式:

1.全权交给 Presenter

View:

@OnClick(R.id.button)
public void goDetail(){
    mPresenter.goDetail(this.getContext);
}

Presenter:

@Override
public void goDetail(Context context){
    Intent intent = new Intent(context, DetailActivity.class);
    intent.putExtra(DetailActivity.Detail, mDetail);
    context.startActivity(intent);
}

2.分离出跳转和获取数据

View:

@OnClick(R.id.button)
public void goDetail(){
    Intent intent = new Intent(context, DetailActivity.class);
    Detail detail = mPresenter.getDetail();
    intent.putExtra(DetailActivity.Detail, detail);
    context.startActivity(intent);
}

Presenter:

@Override
public void getDetail(){
    return mDetail;
}

第一种方式显然在 Presenter 中使用了 Context 来做跳转界面的操作,超越了自己的职责,界面跳转应该由 View 来做,但是跳转需要携带的数据应该是 Presenter 提供的。所以使用第二种方式。

View 应该是被动和无知的

现在有这样一个场景,在第一个 Activity 中点击图片,在第二个 Activity 中请求网络查看大图。

这里有个疑问:View 应该主动调用 loadImg 方法显示图片,还是被动的显示。

在第二个 Activity 中,主动调用 Presenter 的 loadImg(imgUrl),Presenter 再调用 View 的 loadImg(imgUrl),这是 View 主动方法。

在第二个 Activity 中,OnCreate 方法中调用类似 Google MVP Demo 中 Presenter 的 start() 方法,在 Presenter 的 start 方法或其他时机(如,点击加载图片按钮)中调用 mImgUrl = mView.getImgUrl() 获取 ImgUrl,然后调用 mView.loadImg(mImgUrl) 加载图片,这是 View 被动方法。

第一种方法,让人觉得很没必要,自己调了 Presenter 的 loadImg(imgUrl),然后 Presenter 最多做了个 imgUrl 验证就回来调了 View 的 loadImg(imgUrl) 方法,为什么不自己直接调自己的 loadImg(imgUrl) 呢?

Glide 使用在 View 还是 Presenter中

我的答案是 View。

一开始在 View 中使用 Glide.with(this).load(imageUrl).into(imageView);,加载图片时,总会觉得别扭,因为 Glide 明明是 Model 层的东西,根据 MVP 的设计,是不应该由 View 层调用的,View 和 Model 必须通过 Presenter 联系才对。

知道了 Presenter 不该 持有 Context 对象,并且给控件加载图片是属于 View 的操作,所以 Glide 不是属于 Medal 的,而应该理解为 View 层的一个 UIHelper 类。

Glide 是和 View 的生命周期绑定的,那么就必须在 View 中调用,Presenter 是不能包含类似 Activity 或 Fragment 的生命周期方法,这样的话,Presenter 和 View 就耦合在了一起,试想,如果 View 是 Activity 实现的,后来必须换成 Fragment,怎么办?就必须也要改变 Presenter 了,这是很痛苦的耦合现象。

所以 Glide 应该在 View 中调用,而不是 Presenter。

评论