框架模式MVC與MVP在Android中的應用
很多人在開發Android專案時沒有考慮過架構模式的問題,以至於隨著專案的增大,Activty或者Fragment中程式碼也會越來越多,導致專案的維護變的越來越複雜。然而在Android中使用比較多的兩種框架模式就是MVC和MVP,下面我將分別介紹一下這兩種框架模式。
一、MVC框架模式
MVC全名是Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。其中M層處理資料,業務邏輯等;V層處理介面的顯示結果;C層起到橋樑的作用,來控制V層和M層通訊以此來達到分離檢視顯示和業務邏輯層。
其實MVC在J2EE開發中應用是很多的,它將業務邏輯與介面分離開來,通過控制器將其連線,從而達到了很好的解耦效果,通過MVC編寫的程式碼有利於後期的維護和改造。
在Android開發中,我們也是經常看到MVC框架模式的身影,例如我們開發Android端應用時,介面編寫時在XML中,而我們經常回把網路訪問資料獨立出來,再通過Activity處理使用者互動的問題,這就是一種MVC的思想。採用MVC模式的好處是便於UI介面部分的顯示和業務邏輯,資料處理分開。下面具體以MVC的表現形式描述一下:
M層:適合做一些業務邏輯處理,比如資料庫存取操作,網路操作,複雜的演算法,耗時的任務等都在model層處理。
V層:應用層中處理資料顯示的部分,XML佈局可以視為V層,顯示Model層的資料結果。
C層:在Android中,Activity處理使用者互動問題,因此可以認為Activity是控制器,Activity讀取V檢視層的資料(eg.讀取當前EditText控制元件的資料),控制使用者輸入(eg.EditText控制元件資料的輸入),並向Model傳送資料請求(eg.發起網路請求等)。
為方便理解,我下面舉一個例子,由於MVC容易理解,也鑑於篇幅問題,這裡只給出簡短的虛擬碼,因為具體的程式碼也沒有什麼意義:我們通過一個獲取天氣預報資料的小專案來解讀 MVC for Android
Controller控制器
public class MainActivity extends Activity implements StrUIDataListener {
private StrVolleyInterface networkHelper;
private StrVolleyInterface expertNetworkHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//載入網路資料
private void initDate() {
try {
networkHelper = new StrVolleyInterface(DemandDetailActivity.this);
networkHelper.setStrUIDataListener(DemandDetailActivity.this);
ApiClient.getDataDetail(DemandDetailActivity.this, id, networkHelper);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onDataChanged(String data) {
//資料載入成功
//....更新UI
}
@Override
public void onErrorHappened(VolleyError error) {
Toast.makeText(DemandDetailActivity.this, "載入錯誤,請檢查網路!", Toast.LENGTH_SHORT).show();
}
}
從上面程式碼可以看到,Activity持有了ApiClient網路請求模型的物件,當我們需要獲取資料時,只需要呼叫initData()方法。比如我們點選Button,Activity作為Controller控制層會處理View檢視層,並呼叫ApiClient.getDataDetail方法,當Model模型處理資料結束後,通過介面onDataChanged通知View檢視層資料處理完畢,View檢視層該更新介面UI了。然後View檢視層去更新介面。看到這裡,我們會發現整個MVC框架流程就在Activity中體現出來了。
Model模型
這裡ApiClient就是充當Model層的,專門用於處理網路資料請求,程式碼就不展示了,讀者只要理解就行。 上面的例子中,Activity將View檢視顯示和Model模型資料處理隔離開了。activity擔當contronller完成了model和view之間的協調作用。
那麼我們為什麼要這樣實現,直接在Activity中實現網球請求不是更方便嗎?
很多人可能會有這種疑問,可是大家想想,如果這種網路請求都在Activity中實現的話,一是不利於程式碼複用,而是後期維護起來是很困難的。比如我們現在的網路請求用的是Volley,以後想換成NoHttp實現的話,我們好需要修改每一個Activity類,這是不是很繁瑣,看到這裡我們就會發現MVC的好處了。
MVC簡要總結:
在Android專案中,業務邏輯,資料處理等擔任了Model(模型)角色,XML介面顯示等擔任了View(檢視)角色,Activity擔任了Contronller(控制器)角色。contronller(控制器)是一箇中間橋樑的作用,通過介面通訊來協同 View(檢視)和Model(模型)工作,起到了兩者之間的通訊作用。
我們發現,其實控制器Activity主要是起到解耦作用,將View檢視和Model模型分離,雖然Activity起到互動作用,但是Activity中有很多關於檢視UI的顯示程式碼,因此View檢視和Activity控制器並不是完全分離的,也就是說一部分View檢視和Contronller控制器Activity是繫結在一個類中的。所以MVC應用在Android中,它的解耦效果並不是十分的完美,當我們的APP逐漸變大時,我們還會發現我們的Activity和Fragment會越來越龐大。
因此如果我們開發的Android專案不是很龐大,用MVC框架模式還是比較理想的,如果我們的專案是比較龐大,而且後期經常需要修改維護的話,我更建議使用MVP框架模式,下面對MVP框架模式做一個簡單的介紹。
二、MVP框架模式
什麼是MVP
MPV 是從經典的MVC模式演變過來的,其基本思路都是相通的。
MVP是模型(Model)、檢視(View)、控制者(Presenter)的縮寫,分別代表專案中3個不同的模組。
模型(Model):負責處理資料的載入或者儲存,比如從網路或本地資料庫獲取資料等;
檢視(View):負責介面資料的展示,與使用者進行互動;
控制者(Presenter):相當於協調者,是模型與檢視之間的橋樑,將模型與檢視分離開來。
在Andorid專案中,我們習慣將Activity作為MVC中的控制者來達到Model模型和View檢視分離,但是在MVP框架模式中,通常將Activity作為View檢視層,因為在MVC框架模式中Activity和View檢視顯示關聯緊密,Activity中包含大量的View檢視顯示程式碼,如果哪天老闆說需要修改View檢視顯示,這時候你是不是感覺需要修改Activity中的大量程式碼?這麼一來會將Activity中控制邏輯破壞,也導致Activity中承擔太多的職責。根據單一職責原則,Activity主要起到使用者互動作用,也就是接收使用者輸入,顯示請求結果。因此可以通過MVP框架模式來減輕Activity的職責。如下圖所示,View與Model並不直接互動,而是使用Presenter作為View與Model之間的橋樑。其中Presenter中同時持有Viwe層以及Model層的Interface的引用,而View層持有Presenter層Interface的引用。當View層某個介面需要展示某些資料的時候,首先會呼叫Presenter層的某個介面,然後Presenter層會呼叫Model層請求資料,當Model層資料載入成功之後會呼叫Presenter層的回撥方法通知Presenter層資料載入完畢,最後Presenter層再呼叫View層的介面將載入後的資料展示給使用者。這就是MVP模式的整個核心過程。
這樣分層的好處就是大大減少了Model與View層之間的耦合度。一方面可以使得View層和Model層單獨開發與測試,互不依賴。另一方面Model層可以封裝複用,可以極大的減少程式碼量。當然,MVP還有其他的一些優點,這裡不再贅述。下面看下MVP模式在具體專案中的使用。
我舉一個獲取天氣的例子來說明,這樣大家會很容易看懂
Model模型
/**
* 天氣Model介面
*/
public interface WeatherModel {
void loadWeather(String cityNO, OnWeatherListener listener);
}
.........
/**
* 天氣Model實現
*/
public class WeatherModelImpl implements WeatherModel {
@Override
public void loadWeather(String cityNO, final OnWeatherListener listener) {
/*資料層操作*/
VolleyRequest.newInstance().newGsonRequest("http://www.weather.com.cn/data/sk/" + cityNO + ".html",
Weather.class, new Response.Listener<weather>() {
@Override
public void onResponse(Weather weather) {
if (weather != null) {
listener.onSuccess(weather);
} else {
listener.onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onError();
}
});
}
}
和MVC一樣,Model的程式碼不變,只是用與處理網路請求,請求成功後介面回撥將Model模型處理的資料返回給Presenter控制者。
Presenter控制器
/**
* 天氣 Presenter介面
*/
public interface WeatherPresenter {
/**
* 獲取天氣的邏輯
*/
void getWeather(String cityNO);
}
..........
/**
* 在Presenter層實現,給Model層回撥,更改View層的狀態,確保Model層不直接操作View層
*/
public interface OnWeatherListener {
/**
* 成功時回撥
*
* @param weather
*/
void onSuccess(Weather weather);
/**
* 失敗時回撥,簡單處理,沒做什麼
*/
void onError();
}
.........
/**
* 天氣 Presenter實現
*/
public class WeatherPresenterImpl implements WeatherPresenter, OnWeatherListener {
/*Presenter作為中間層,持有View和Model的引用*/
private WeatherView weatherView;
private WeatherModel weatherModel;
public WeatherPresenterImpl(WeatherView weatherView) {
this.weatherView = weatherView;
weatherModel = new WeatherModelImpl();
}
@Override
public void getWeather(String cityNO) {
weatherView.showLoading();
weatherModel.loadWeather(cityNO, this);
}
@Override
public void onSuccess(Weather weather) {
weatherView.hideLoading();
weatherView.setWeatherInfo(weather);
}
@Override
public void onError() {
weatherView.hideLoading();
weatherView.showError();
}
}
從程式碼中我們可以看到Presenter控制器同時持有 WeatherModel和WeatherView物件且實現了OnWeatherListener介面取回Model模型資料,因此,WeatherPresenterImpl向WeatherModel傳送資料請求,然後通過OnWeatherListener介面實現獲取請求結果,在將結果通過介面WeatherView把資料顯示到Activity擔當的View檢視中。從而達到徹底將Model和View完全分離,試想在這種情況下,如果你需要修改Model是完全不會影響View檢視程式碼的修改的,同理,修改View檢視層的時候,也完全無需修改Model層。相當於Model和View互相不知道對方的存在,都是通過中間控制器Presenter來傳達通訊。
View檢視
先定義一個View檢視顯示的介面WeatherView
新聞列表模組主要是展示從網路獲取的新聞列表資訊,View層的介面大概需要如下方法:
(1)載入資料的過程中需要提示“正在載入”的反饋資訊給使用者
(2)載入成功後,將載入得到的資料填充到RecyclerView展示給使用者
(3)載入成功後,需要將“正在載入”反饋資訊取消掉
(4)若載入資料失敗,如無網路連線,則需要給使用者提示資訊
根據上面描述,我們將View層的介面定義如下,分別對應上面四個方法:
public interface WeatherView {
void showLoading();
void hideLoading();
void showError();
void setWeatherInfo(Weather weather);
}
然後實現Activity實現WeatherView介面/**
* 天氣介面
*/
public class WeatherActivity extends BaseActivity implements WeatherView, View.OnClickListener {
..........................
private WeatherPresenter weatherPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
<span style="white-space:pre"> </span>....................
findView(R.id.btn_go).setOnClickListener(this);
weatherPresenter = new WeatherPresenterImpl(this); //傳入WeatherView
loadingDialog = new ProgressDialog(this);
loadingDialog.setTitle("載入天氣中...");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
weatherPresenter.getWeather(cityNOInput.getText().toString().trim());
break;
}
}
@Override
public void showLoading() {
loadingDialog.show();
}
@Override
public void hideLoading() {
loadingDialog.dismiss();
}
@Override
public void showError() {
//Do something
Toast.makeText(getApplicationContext(), "error", Toast.LENGTH_SHORT).show();
}
@Override
public void setWeatherInfo(Weather weather) {
WeatherInfo info = weather.getWeatherinfo();
//更新介面
.....................
}
}
總結:
MVP框架模式完全將Model模型和View檢視分離,從而使得程式碼的耦合低,利用MVP框架寫專案達到解耦作用。 MVP和MVC最大的區別是:MVC中的V和C關係比較緊密,耦合度太高,從C中訪問M獲取資料一定程度上也可以看成從V中訪問M。而MVP中M和V完全分離,互相不知道對方的存在,Presenter通過介面通訊方式將V和M通訊。 在Android中MVP框架 Activity擔當View檢視層,MVC框架模式Activity擔當控制器。
雖然MVP可以達到很好的解耦效果,可是在開發的過程中需要多寫一層(Presenter)的程式碼,程式碼量會擴大,而且業務邏輯對不太熟悉的開發者來說還是略顯複雜,因此建議如果開發相對較小的專案,而且專案不需要頻繁的修改可以選用MVC,如果開發比較大的專案,而且需要後期不斷地維護修改的話建議使用MVP框架模式開發。