Android中MVP設(shè)計(jì)框架淺析

一庇谆、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)目的大小合理的選擇框架就夠了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殷绍,一起剝皮案震驚了整個(gè)濱河市挠他,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篡帕,老刑警劉巖殖侵,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镰烧,居然都是意外死亡拢军,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門怔鳖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茉唉,“玉大人,你說我怎么就攤上這事结执《嚷剑” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵献幔,是天一觀的道長懂傀。 經(jīng)常有香客問我,道長蜡感,這世上最難降的妖魔是什么蹬蚁? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任郑兴,我火速辦了婚禮犀斋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘情连。我一直安慰自己叽粹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虫几,像睡著了一般锤灿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上持钉,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天衡招,我揣著相機(jī)與錄音篱昔,去河邊找鬼每强。 笑死,一個(gè)胖子當(dāng)著我的面吹牛州刽,可吹牛的內(nèi)容都是我干的空执。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼穗椅,長吁一口氣:“原來是場噩夢啊……” “哼辨绊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匹表,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤门坷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后袍镀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體默蚌,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年苇羡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绸吸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡设江,死狀恐怖锦茁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叉存,我是刑警寧澤码俩,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站歼捏,受9級特大地震影響握玛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甫菠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一挠铲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寂诱,春花似錦拂苹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浴韭。三九已至,卻和暖如春脯宿,著一層夾襖步出監(jiān)牢的瞬間念颈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工连霉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榴芳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓跺撼,卻偏偏與公主長得像窟感,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子歉井,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,116評論 25 707
  • OEC H2O-> O2 NQE proton or H Mn valence alters rate exp ~...
    Liming_Liu閱讀 179評論 0 0
  • 夜色像一張烤糊的煎餅 在窗戶攤開 秋風(fēng)馱著越來越重的寒涼 驟雨撞擊著玻璃 猶如狂奔的馬蹄掠過我孤獨(dú)的內(nèi)心 想你了柿祈,...
    致遠(yuǎn)_8493閱讀 229評論 0 2
  • 偶然因工作需要,在爬取數(shù)據(jù)時(shí) selenium+phantomJS 并不能做到我們想要的效果(猜想可能phanto...
    Mercury今閱讀 320評論 1 4
  • 你們不相信的 我相信 你們不理解的 我理解 校園本來應(yīng)該是干凈的 被一些人的謾罵聲詛咒 現(xiàn)在應(yīng)該清掃清掃了 落葉歸...
    薔薇颯閱讀 210評論 0 0