通過程式碼對比,詳細講解MVC,MVP,MVVM之間應該如何選擇,以及對Android單元測試的探索。本文的側重點在於如何選擇,並沒有對每種架構模式概念展開詳解(網路上這方面的文章有很多,大家可以自行搜尋)。
大綱
- MVC or MVP or MVVM?
- 確定選型:MVP + DataBinding
- 單元測試(探索階段)
目的
- 提高開發效率
- 易於測試
- 擁抱變化
- 降低維護成本
Android中的MVC
示例:
展示任務詳情的功能,詳情View層的通過xml來寫的,請求資料相關的程式碼會在Model層提供介面,然後通過Activity對View和Model層進行連線。
程式碼:
M:
public interface TaskModel {
void loadTask(String taskId, OnTaskListener listener);
}
V:
taskdetail.xml:介面佈局檔案,採用XML進行描述,屬於V層的一部分。
C & V:
TaskDetailActivity:C層和V層
public class TaskDetailActivity {
private void initView() {
taskModel.loadTask(taskId, new OnTaskListener() {
public void onSuccess(Task task) {
//V層程式碼,但是目前耦合到了C層
detailTitle.setText(task.getTitle);
}
});
}
}複製程式碼
總結:
- 編寫簡單快速,適合含有簡單邏輯的業務,或是demo程式。
- Activity的臃腫:xml作為view層,控制能力太弱,無法動態的改變頁面的內容,只能把程式碼寫在activity中,造成了activity既是Controller層,又是View層的問題。
- 耦合度較高,需求變化改動大,後續維護成本高。
- Controller混雜著Android程式碼無法Junit。
Android中的MVP
示例:
展示任務詳情的功能,詳情View層的通過xml和Activity來完成,請求資料相關的程式碼會在Model層提供介面,然後通過Presenter對View和Model層進行連線。
程式碼:
M:
public interface TaskModel {
void loadTask(String taskId, OnTaskListener listener);
}
V:
taskdetail.xml:介面佈局檔案,採用XML進行描述,屬於V層的一部分。
public class TaskDetailActivity implements TaskDetailView {
public void showTask(Task task) {
detailTitle.setText(task.getTitle);
}
}
P:
public class TaskDetailPresenter implements Presenter {
public void getTask() {
taskModel.loadTask(taskId, new OnTaskListener() {
public void onSuccess(Task task) {
//通過介面回撥到V層更新UI
taskDetailView.showTask(task);
}
});
}
}複製程式碼
總結:
- 減少各層之間耦合,易於後續的需求變化,降低維護成本。
- Presenter層獨立於Android程式碼之外,可以進行Junit測試。
- 介面和類較多,互相做回撥,程式碼臃腫。
- Presenter層與View層是通過介面進行互動的,介面粒度不好控制。
Android中的MVVM
示例:
展示任務詳情的功能,詳情View層的通過xml和Activity來完成,請求資料相關的程式碼會在Model層提供介面,然後通過ViewModel對View和Model層進行連線。
程式碼:
M:
public interface TaskModel ...
void loadTask(String taskId, OnTaskListener listener);
V:
taskdetail.xml:介面佈局檔案,採用XML進行描述,繫結規則在xml中進行定義。
TaskDetailActivity:Activity主要是初始化和補充的功能。
VM:
TaskDetailViewModel {
public void getTask() {
taskModel.loadTask(taskId, new OnTaskListener() {
public void onSuccess(Task task) {
//通過繫結技術更新UI,做到資料獨立於UI
taskDeatailViewBinding.setTask(task);
}
});
}
}複製程式碼
總結:
- 和MVP比較像,主要區別在於View和ViewModel / Presenter之間的通訊
- 相比MVP優勢是通過DataBinding技術為VM和V層進行資料繫結,提高開發效率,由於目前繫結技術的侷限,V層一些介面的處理還是需要Activity的輔助。
- VM層摻雜Android程式碼無法進行Junit測試。
確定選型
通過以上對比,選擇了MVP+DataBinding,此架構模式基於MVP,並使用DataBinding庫來顯示資料並繫結View。它並不遵循嚴格的MVVM或MVP模式,因為它同時使用了ViewModel和Presenter。
DataBinding
這是我上篇文章我們為什麼要使用DataBinding,裡面通過程式碼的對比,總結說明為什麼要使用DataBinding的技術,有興趣的同學可以閱讀一下,在這裡我把文章裡的一小段總結貼出來:
DataBinding為資料驅動:資料變化後自動更新UI;事件處理:直接找到目標例項處理使用者操作的事件。這樣我們就不需要和UI或者控制元件打交道,只需要在java程式碼中處理業務邏輯就好了,非常清晰,其餘的統一交給binding庫去完成。降低了程式碼耦合度,使得資料獨立於UI,對以後程式的變化和維護都有積極的影響。
MVP+DataBinding
示例:
展示任務詳情的功能,資料和事件繫結與基礎MVP程式碼的對比。
程式碼:
//普通MVP 任務詳情程式碼示例:
public void onCreateView(...) {
detailTitle = (TextView) root.findViewById(R.id.task_detail_title);
detailComplete = (CheckBox) root.findViewById(R.id.task_detail_complete);
detailComplete.setOnCheckedChangeListener(
(cb, isChecked) -> presenter.completeChanged(task, isChecked)
);
}
@Override
public void showDescription(String title) {
detailTitle.setText(title);
}
//xml檔案省略...
//MVP+DataBinding 任務詳情程式碼示例:
@Override
public void showTask(Task task) {
viewDataBinding.setTask(task);
}
//可以通過佈局檔案直接繫結到資料模型的屬性(xml檔案)
<TextView
android:id="@+id/task_detail_title"
android:text="@{task.title}" />
//事件繫結同樣可以直接在佈局檔案中實現(xml檔案)
<CheckBox
android:id="@+id/task_detail_complete"
android:checked="@{task.completed}"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />複製程式碼
總結:
- DataBinding庫提高了開發效率,使得xml佈局檔案用於將資料繫結到UI元素,也可以繫結一個action handler(Presenter)處理使用者操作的事件,可以觀察和設定資料,以便在需要時自動更新(雙向繫結)。
- 需要對View和Presenter兩層做測試,增加工作量。
- 目前Android Studio對Databing的支援不是太好(報錯和程式碼自動生成)
單元測試(探索階段)
Presenter
不需要Android環境,因此使用Junit測試即可
- JUnit:Java語言的單元測試框架
- Mockito:模擬物件的測試框架
View
使用Google建議的Espresso進行UI的測試(需要依賴Android環境)
- AndroidJUnitRunner:Android 且與 JUnit 4 相容的測試執行器
- Espresso:功能性 UI 測試框架
問題
就目前實踐過程中,會發現需要為測試寫一些額外的方法,不是太舒服。並且寫兩層測試,工作量變大。還有覆蓋率以及維護的問題,一直在探索最佳實踐,會在以後的文章裡面和大家分享。
本片文章來自於自己的程式設計實戰,寫的不好的地方請大家幫忙指正,希望能幫助大家選到合適自己的架構模式。
謝謝閱讀。