一庇谆、MVP設(shè)計(jì)模式簡介
目前Android設(shè)計(jì)成熟的框架有MVC缀台,MVP和MVVM棠赛,MVP是由MCV演變而來,MVVM是MVP的進(jìn)一步升級膛腐。三種框架模式并沒有完全的哪一種最好睛约,只有在項(xiàng)目的需求上哪一種框架最適合。根據(jù)前人總結(jié)的項(xiàng)目經(jīng)驗(yàn)哲身,小項(xiàng)目用MVC辩涝,中型項(xiàng)目用MVP,大型項(xiàng)目用MVVM勘天。
二怔揩、MVP版本的演進(jìn)史
2.1 MVC在Android開發(fā)中的局限
MVP模式是由MVC發(fā)展演變而來的,MVC模式的UML圖存在如下兩個(gè)版本脯丝。其中版本一參見于MVP的百度百科和標(biāo)準(zhǔn)的MVC網(wǎng)絡(luò)教程商膊;版本二可以見于部分Android MVC和MVP的介紹網(wǎng)站。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?MVC 版本一
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MVC 版本二
標(biāo)準(zhǔn)MVC在軟件開發(fā)中的流程設(shè)定是巾钉,view層負(fù)責(zé)界面顯示邏輯翘狱,Control層負(fù)責(zé)業(yè)務(wù)流程邏輯秘案,Model層負(fù)責(zé)數(shù)據(jù)的存取邏輯砰苍。view層持有Control層的引用潦匈,Control層持有Model層的引用,Control層持有View層的引用赚导,當(dāng)View層有用戶操作需要獲取數(shù)據(jù)時(shí)茬缩,會通過Control層控制Model層進(jìn)行數(shù)據(jù)的獲取,最后在通過Model層回顯到View層吼旧。MVC實(shí)現(xiàn)了在界面凰锡、業(yè)務(wù)邏輯和數(shù)據(jù)的分層設(shè)計(jì),在改進(jìn)這三層設(shè)計(jì)中的某一層設(shè)計(jì)時(shí)圈暗,不需要重新編寫整個(gè)代碼掂为。
在Android的設(shè)計(jì)中,通常是用一個(gè)Activity組件設(shè)計(jì)一個(gè)頁面的交互邏輯员串。在Activity的頁面設(shè)計(jì)中勇哗,并不容易將View和Controller進(jìn)行分層處理,這就導(dǎo)致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);
}
//顯示結(jié)果
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)內(nèi)的代碼就負(fù)責(zé)了業(yè)務(wù)層的部分邏輯渺鹦,并且很明顯可以看到Model層的引用weatherModel扰法。
上述代碼中只要有一個(gè)按鈕點(diǎn)擊的事件,當(dāng)Activity頁面存在多個(gè)事件操作時(shí)毅厚,Activity的代碼必定會顯得非常臃腫塞颁,這導(dǎo)致的結(jié)果就是破壞了MVC原本的分層邏輯設(shè)計(jì),任何一處代碼的修改都會帶來較大的代碼改動吸耿。
2.2 Android中MVP設(shè)計(jì)模式的引入
鑒于MVC在Android設(shè)計(jì)上出現(xiàn)的缺陷殴边,因此在設(shè)計(jì)框架上引入了MVP,MVP的UML圖下
從UML圖可以看出珍语,在MVP設(shè)計(jì)模式中锤岸,Presenter層完全隔離了View層和Model層,主要的程序邏輯在Presenter里進(jìn)行實(shí)現(xiàn)板乙。
以Google提供的MVP代碼([https://github.com/googlesamples/android-architecture/tree/todo-mvp/](https://github.com/googlesamples/android-architecture/tree/todo-mvp/))來進(jìn)行分析是偷。
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層的代碼設(shè)計(jì)
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());
}
}
從代碼的設(shè)計(jì)上可以看出View層邏輯上相關(guān)的代碼都交由Presenter的引用在進(jìn)行處理,在Presenter層中持有View層和Model層的引用募逞,Presenter層可以回調(diào)更新UI界面蛋铆。在Google提供代碼的Data目錄下,并沒有看到持有Presenter層的引用放接,這一點(diǎn)并不違反MVP的設(shè)計(jì)框架刺啦,MVP的設(shè)計(jì)框架的思想是為了對view、model和presenter進(jìn)行分層纠脾,能夠在代碼設(shè)計(jì)中實(shí)現(xiàn)這一點(diǎn)已經(jīng)足夠了玛瘸。另外在代碼設(shè)計(jì)中為了秉承接口隔離的設(shè)計(jì)原則蜕青,在MVP的代碼中采用很多接口進(jìn)行耦合。
三糊渊、MVP設(shè)計(jì)的總結(jié)
MVP是在MVC上的改進(jìn)右核,這并非代表著在Android設(shè)計(jì)中MVP設(shè)計(jì)模式一定比MVC好,比如在某些項(xiàng)目中渺绒,數(shù)據(jù)從Model層經(jīng)過Presenter層拷貝到View層的開銷比較大贺喝,這種情況下可能需要使用MVC的更為合適,通過定義一個(gè)接口也可以起到一定的隔離和分層左右宗兼。在代碼設(shè)計(jì)中需要做的是盡可能的秉承設(shè)計(jì)的六項(xiàng)基本原則躏鱼,根據(jù)項(xiàng)目的大小合理的選擇框架就夠了。