前言:这是《Typical Questions about MVP Pattern》这篇文章的译文。原作者songzhw。
介绍:
MVP现在很火,有很多开发者都知道MVP。我也在我们公司的项目中引入了MVP。我的同事们刚开始是不太乐意接受这种模式,但是,他们很快就发现了MVP的优点。我很乐意看到这件事。然而,我发现我的同事在写关于MVP的代码的时候会有一些问题。今天我尝试着解释一些关于MVP的典型问题。
问题1:哪些东西是属于Presenter(P层)的?
我们习惯在使用view,controller和分离的Model层去组建Activity和Fragment。现在撇开Model,如果是逻辑代码,那么它属于Presenter。另外呢,如果是用来刷新或者创建views的代码,它肯定属于View。
问题2:Activity是Presenter或者View么?
好吧,这真是一个好问题。
大部分人视Activity和Fragment为View,为其创建一个有关联的Presenter。那是因为Activity和Fragment可以使用"findViewById()"和"onTouchEvent()"这些似乎是view的代码。Google的MVP示例也是这样的。
在一些人看来,当Activity和Fragment仅仅使用"startActivity()" 和"startActivityForResult()"时,Activity和Fragment就是一个Controller。
因为Activity设计的并不是很好。理想上来讲,Activity不必同时包含view的方法和controller的方法。那么,对于我们应用程序开发人员应该怎么去做?
就像"Vim 和 Emacs"(Vim,Emacs都是高级文本编辑器),我真的不能说那个更强大:
*如果将Activity/Fragment作为View,就创建一个Presenter进行逻辑处理。
*如果将Activity/Fragment作为Controller,就创建一个View类进行界面的显示和刷新。
这都是你可以使用的方案。但是不要同时使用,否则你的代码将会是一团乱麻。
问题3:Fragment是否需要一个Presenter?
这需要看情况而定。
如果你的Fragment仅仅是一个View,而不是一个全屏的页面,那就不需要相关的Presenter。
如果你的Activity有多个页面,而且每个页面都一个全屏的Fragment,像这样的Fragment就需要不同的Presenter。
问题4:Adapter是否需要Presenter?
正常的情况是不需要的。
这是因为我们想把一个页面的逻辑都放在一起,方便进行数据的访问和修改。Adapter通常是和AdapterView/ViewPager/RecyclerView一起的。这就像我是的问题3,一个单一的View是不需要Presenter的。
如果我们的Adapter需要处理逻辑,就像这样的?
// data : List
if(data.get(position).isLegal()){
view.setText("Legal User")
} else {
view.setText("Illgal User")
}
如果可以,更好的代码是将逻辑提取到Presenter,Adapter只做显示的工作。
Presenter
NewItem newItem = new NewItem(rawItem);
if(rawItem.isLegal()){
newItem.title="Legal User"
} else {
newItem.title = "Ilgal user"
}Adapter
// data : List
view.setText(data.get(position).getTitle())
这种方式,你的逻辑就提取到Presenter中了。如果有新的需求需要更改逻辑,你就不需要去找这个逻辑代码了,因为我们都知道它一定在Presenter中。
问题5:Presenter是否需要Context对象?
通常情况,Presenter是一个纯Java类。只有在Presenter是一个纯Java类的时候,我们可以通过JUnit测试逻辑。
PS:当然,你也可以在Robolectric(一个单元测试框架)的帮助下对你的Android代码进行单元测试。但是,Presenter必须是一个纯Java类。但是不能保证Robolectric每次都正常运行。实际上,我们的项目都没有超过3个月,就是因为我们用了Robolectric,使用Jenkins(项目自动化构建工具)的时候偶尔会因为Robolectric出错。我们不得不将所有的Robolecric 测试用例添加到忽略里面。
现在回到我们的问题,Presenter必须是一个纯Java类,但我们的Presenter可能需要一个Context对象,比方说获取一个字符串或者一个dimen值?
这种情况,我们就在Activity(View)中获取值,然后传递给Presenter。
或者,我们在View中提供“getString()”方法,然后在Presenter中调用这个方法获取值。
pbulic interface IView{
...
String getString(int id);
}
然后,当你想测试Presenter时,它调用起来就很简单了,因为在Presenter中的IView只是一个借口。
问题6:在Presenter中如何复用代码?
很简单,就像其它代码一样,你创建一个类或者方法来使得代码可以复用,在你需要的Presenter中调用这些类或者方法。
就像这个例子。两个Presenter都需要一个数据库工具来处理数据库。
public class FilesPresenter{
private DatabaseHelper helper;
}
public class BooksPresenter{
private DatabaseHelper helper;
}
问题7:为什么MVP让你代码的可测试性更强?
-通过将View和逻辑分离,我们现在可以只对Presenter和逻辑进行单元测。单元测试将会比自动化测试更快(Instrumentation 和Espresso都是Android可以用的测试工具),还有可以不使用设备(这里应该指手机等设备)进行测试了。
-使用接口,而不是真正的引用Activity或者Fragment,mock(这里应该是指多态,就是并不是new一个Activity/Fragment,而是通过多态的方式) View对象将会更容易。同样的,对mock Presenter也会很容易。应为Presenter只引用了Activity/Fragment的接口。
PS:一些开发人员可能会说,我直接new一个对象很简单,为什需要使用mock。因为你的对象很容易(这个可能是作者写错了,我认为应该是很难被初始化)被初始化。一些类需要两三个类来进行初始化。一些类需要依赖更多的类或者需要http/数据库连接。这使得很难初始化这样一个类。解决方法就是mock,mock可以帮你切断所有的依赖关系,使得初始化一个对象变得很容易。
一个典型的场景。
现在我们有一个RecyclerView用来显示后台数据。后台的数据是这样的:
List
Each "Country" class contains : List
我们需要将“Country-Province”显示在网格布局里面。
我们应该如果使用MVP处理这样的情况?
我们应该做的就是:
1,创建一个UI逻辑类,叫做“CellInfo”
2,为我们的Activity创建一个Presenter(不是给Adapter的)
3,Activity调用“presenter.getDataFromServer()”
4,Presenter.getDataFromServer()将会去获取数据。在回调里面去刷新List的数据。在这里,每个CellInfo实际上是“$country – $province”的值。
5,转换之后,Presenter调用“view.onRefrshList(cellInfoList)”。
6,Activity.onRefreshList(List):通过cellInfoList 的Adapter,所有,Adapter只关心View的展示,不用关心逻辑。
参考:
https://www.youtube.com/watch?v=QrbhPcbZv0I 这是一个非常棒的讲解MVP的视屏。演讲者将会带你一步一步的步入MVP的仙境。我强烈推荐你看这个视频。