我們有可能使用過MVC(Model—View—Controller)模式,但當我們用更優的方法測試Android代碼時,使用MVP(模型—視圖—主導器:Model—View—Presenter)模式可能更合適。MVP模式與MVC模式的根本區別是:在MVP模式中,視圖中的業務邏輯被放入主導器中,主導器通過接口與視圖交互。在MVC模式中,視圖可以包含訪問模型的邏輯。在MVP模式中,視圖與模型是隔離的,所有與視圖和模型的交互操作都是在主導器中完成,因此主導器在整個MVP模式中處於“主導”地位。
在接下來的demo裏,我會展示如何在Android中使用MVP模式,以及如何利用該模式提高代碼的易測性。爲了演示其運行機制,我們創建一個啓動畫面。所謂啓動畫面,就是一個普通的界面,在應用程序開始運行前做一些初始化和驗證工作。在本例中,我們會在啓動畫面中檢查網絡連接是否正常,並顯示一個進度條。如果網絡連接正常,就切換到另一個Activity中;否則便不會切換到其他Activity,而是向用戶顯示一條錯誤信息以阻止程序繼續運行。
要創建啓動畫面,需要一個負責在模型和視圖間交互的主導器。在本例中,主導器有兩個功能:一個功能用於判斷網絡是否連接,另一個功能用於控制視圖。
主導器中會用到一個模型類ConnectionStatus,該類實現了IConnectionStatus接口,該接口中只定義了一個判斷網絡是否在線的方法。源碼如下所示:
public interface IConnectionStatus {
boolean isOnline();
}
負責控制視圖的代碼位於Activity中,並且這個Activity實現了ISplashView接口。主導器會通過該接口控制應用程序的執行過程。ISplashView接口的源碼如下所示:
public interface ISplashView {
void showProgress();
void hideProgress();
void showNoInetErrorMsg();
void moveToMainView();
}
因爲我們是在Android平臺上開發應用程序,因此首先需要創建視圖,然後我們會把視圖的控制權交給主導器。代碼如下所示:
public class SplashActivity extends Activity implements ISplashView {
private TextView mTextView;
private ProgressBar mProgressBar;
private SplashPresenter mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
//爲當前Activity初始化主導器,並將當前Activity設置給主導器
mPresenter = new SplashPresenter();
mPresenter.setView(this);
//Activity初始化代碼
mTextView = (TextView) findViewById(R.id.splash_text);
mProgressBar = (ProgressBar) findViewById(R.id.splash_progress_bar);
}
@Override
protected void onResume() {
super.onResume();
//運行onResume()方法時,通知主導器當前視圖已經準備完畢,可以把控制權交給主導器了
mPresenter.didFinishLoading();
}
@Override
public void showProgress() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
mProgressBar.setVisibility(View.INVISIBLE);
}
@Override
public void showNoInetErrorMsg() {
mTextView.setText("No internet");
}
@Override
public void moveToMainView() {
startActivity(new Intent(this, MainActivity.class));
}
}
主導器的代碼比較簡單,其源碼如下所示:
public class SplashPresenter {
private IConnectionStatus mConnectionStatus;
private ISplashView mView;
public SplashPresenter() {
this(new ConnectionStatus());
}
public SplashPresenter(IConnectionStatus connectionStatus) {
mConnectionStatus = connectionStatus;
}
public void setView(ISplashView view) {
this.mView = view;
}
protected ISplashView getView() {
return mView;
}
public void didFinishLoading() {
//獲取視圖,即設置給主導器的ISplashView接口的實現類的引用
ISplashView view = getView();
//判斷程序是否繼續執行的邏輯
if (mConnectionStatus.isOnline()) {
view.moveToMainView();
} else {
view.hideProgress();
view.showNoInetErrorMsg();
}
}
}
從上述代碼可以看出,主導器通過接口訪問視圖,主導器並不知道該接口是由Activity實現的。這樣,在單元測試中就更容易模擬(mock)視圖。
MVP模式可以使代碼更易組織且更易測試。在上述demo中,有一個測試文件夾,在測試代碼中需要初始化主導器並模擬(mock)接口。在主導器中並未使用任何Android平臺相關的代碼,因此不需要在Android設備生運行測試用例,只需要在JVM上運行即可。此外,在本例中我們使用Mockito模擬接口。
在Android平臺上開發應用程序,我們會發現Activity中會存在大量代碼。遺憾的是,測試Activity是很痛苦的。使用MVP模式不僅可以簡化創建測試用例的過程,還可以更容易地實施TDD(test-driven development,測試驅動開發)。