一、MVP设计模式简介
目前Android设计成熟的框架有MVC,MVP和MVVM,MVP是由MCV演变而来,MVVM是MVP的进一步升级。三种框架模式并没有完全的哪一种最好,只有在项目的需求上哪一种框架最适合。根据前人总结的项目经验,小项目用MVC,中型项目用MVP,大型项目用MVVM。
二、MVP版本的演进史
2.1 MVC在Android开发中的局限
MVP模式是由MVC发展演变而来的,MVC模式的UML图存在如下两个版本。其中版本一参见于MVP的百度百科和标准的MVC网络教程;版本二可以见于部分Android MVC和MVP的介绍网站。
MVC 版本一
MVC 版本二
标准MVC在软件开发中的流程设定是,view层负责界面显示逻辑,Control层负责业务流程逻辑,Model层负责数据的存取逻辑。view层持有Control层的引用,Control层持有Model层的引用,Control层持有View层的引用,当View层有用户操作需要获取数据时,会通过Control层控制Model层进行数据的获取,最后在通过Model层回显到View层。MVC实现了在界面、业务逻辑和数据的分层设计,在改进这三层设计中的某一层设计时,不需要重新编写整个代码。
在Android的设计中,通常是用一个Activity组件设计一个页面的交互逻辑。在Activity的页面设计中,并不容易将View和Controller进行分层处理,这就导致View层会持有Model层的引用,就形成了MVC版本二的UML图。参见图下示例代码,
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
private WeatherModel weatherModel;
private EditText cityNOInput;
private TextView city;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
weatherModel = new WeatherModelImpl();
initView();
}
//初始化View
private void initView() {
cityNOInput = findView(R.id.et_city_no);
city = findView(R.id.tv_city);
...
findView(R.id.btn_go).setOnClickListener(this);
}
//显示结果
public void displayResult(Weather weather) {
WeatherInfo weatherInfo = weather.getWeatherinfo();
city.setText(weatherInfo.getCity());
...
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
break;
}
}
@Override
public void onSuccess(Weather weather) {
displayResult(weather);
}
@Override
public void onError() {
Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
}
private T findView(int id) {
return (T) findViewById(id);
}
}
在代码中onClick(View v)内的代码就负责了业务层的部分逻辑,并且很明显可以看到Model层的引用weatherModel。
上述代码中只要有一个按钮点击的事件,当Activity页面存在多个事件操作时,Activity的代码必定会显得非常臃肿,这导致的结果就是破坏了MVC原本的分层逻辑设计,任何一处代码的修改都会带来较大的代码改动。
2.2 Android中MVP设计模式的引入
鉴于MVC在Android设计上出现的缺陷,因此在设计框架上引入了MVP,MVP的UML图下
从UML图可以看出,在MVP设计模式中,Presenter层完全隔离了View层和Model层,主要的程序逻辑在Presenter里进行实现。
以Google提供的MVP代码([https://github.com/googlesamples/android-architecture/tree/todo-mvp/](https://github.com/googlesamples/android-architecture/tree/todo-mvp/))来进行分析。
View层的代码如下
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
@NonNull
private static final String ARGUMENT_TASK_ID = "TASK_ID";
@NonNull
private static final int REQUEST_EDIT_TASK = 1;
private TaskDetailContract.Presenter mPresenter;
private TextView mDetailTitle;
private TextView mDetailDescription;
private CheckBox mDetailCompleteStatus;
public static TaskDetailFragment newInstance(@Nullable String taskId) {
Bundle arguments = new Bundle();
arguments.putString(ARGUMENT_TASK_ID, taskId);
TaskDetailFragment fragment = new TaskDetailFragment();
fragment.setArguments(arguments);
return fragment;
}
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.taskdetail_frag, container, false);
setHasOptionsMenu(true);
mDetailTitle = (TextView) root.findViewById(R.id.task_detail_title);
mDetailDescription = (TextView) root.findViewById(R.id.task_detail_description);
mDetailCompleteStatus = (CheckBox) root.findViewById(R.id.task_detail_complete);
// Set up floating action button
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.editTask();
}
});
return root;
}
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_delete:
mPresenter.deleteTask();
return true;
}
return false;
}
...
@Override
public void showCompletionStatus(final boolean complete) {
Preconditions.checkNotNull(mDetailCompleteStatus);
mDetailCompleteStatus.setChecked(complete);
mDetailCompleteStatus.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mPresenter.completeTask();
} else {
mPresenter.activateTask();
}
}
});
}
...
}
Presenter层的代码设计
package com.example.android.architecture.blueprints.todoapp.taskdetail;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.example.android.architecture.blueprints.todoapp.data.Task;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository;
import com.google.common.base.Strings;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Listens to user actions from the UI ({@link TaskDetailFragment}), retrieves the data and updates
* the UI as required.
*/
public class TaskDetailPresenter implements TaskDetailContract.Presenter {
private final TasksRepository mTasksRepository;
private final TaskDetailContract.View mTaskDetailView;
@Nullable
private String mTaskId;
public TaskDetailPresenter(@Nullable String taskId,
@NonNull TasksRepository tasksRepository,
@NonNull TaskDetailContract.View taskDetailView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
mTaskDetailView.setPresenter(this);
}
@Override
public void start() {
openTask();
}
private void openTask() {
if (Strings.isNullOrEmpty(mTaskId)) {
mTaskDetailView.showMissingTask();
return;
}
mTaskDetailView.setLoadingIndicator(true);
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
mTaskDetailView.showMissingTask();
} else {
showTask(task);
}
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
@Override
public void editTask() {
if (Strings.isNullOrEmpty(mTaskId)) {
mTaskDetailView.showMissingTask();
return;
}
mTaskDetailView.showEditTask(mTaskId);
}
@Override
public void deleteTask() {
if (Strings.isNullOrEmpty(mTaskId)) {
mTaskDetailView.showMissingTask();
return;
}
mTasksRepository.deleteTask(mTaskId);
mTaskDetailView.showTaskDeleted();
}
@Override
public void completeTask() {
if (Strings.isNullOrEmpty(mTaskId)) {
mTaskDetailView.showMissingTask();
return;
}
mTasksRepository.completeTask(mTaskId);
mTaskDetailView.showTaskMarkedComplete();
}
@Override
public void activateTask() {
if (Strings.isNullOrEmpty(mTaskId)) {
mTaskDetailView.showMissingTask();
return;
}
mTasksRepository.activateTask(mTaskId);
mTaskDetailView.showTaskMarkedActive();
}
private void showTask(@NonNull Task task) {
String title = task.getTitle();
String description = task.getDescription();
if (Strings.isNullOrEmpty(title)) {
mTaskDetailView.hideTitle();
} else {
mTaskDetailView.showTitle(title);
}
if (Strings.isNullOrEmpty(description)) {
mTaskDetailView.hideDescription();
} else {
mTaskDetailView.showDescription(description);
}
mTaskDetailView.showCompletionStatus(task.isCompleted());
}
}
从代码的设计上可以看出View层逻辑上相关的代码都交由Presenter的引用在进行处理,在Presenter层中持有View层和Model层的引用,Presenter层可以回调更新UI界面。在Google提供代码的Data目录下,并没有看到持有Presenter层的引用,这一点并不违反MVP的设计框架,MVP的设计框架的思想是为了对view、model和presenter进行分层,能够在代码设计中实现这一点已经足够了。另外在代码设计中为了秉承接口隔离的设计原则,在MVP的代码中采用很多接口进行耦合。
三、MVP设计的总结
MVP是在MVC上的改进,这并非代表着在Android设计中MVP设计模式一定比MVC好,比如在某些项目中,数据从Model层经过Presenter层拷贝到View层的开销比较大,这种情况下可能需要使用MVC的更为合适,通过定义一个接口也可以起到一定的隔离和分层左右。在代码设计中需要做的是尽可能的秉承设计的六项基本原则,根据项目的大小合理的选择框架就够了。