一,前言:
關于Android程序的構架, 目前基本上可以分為三類,即MVC、MVP、MVVM。當前MVP和MVVM的使用相對比較廣泛,當然MVC也并沒有過時之說。它們各有各的特點,具體如何使用要結合實際項目中來確定,架構模式本身沒有好壞之分,我們只有了解了這些架構設計的特點之后,才能在進行開發的時候選擇適合自己項目的架構模式,這也是本文的目的。
二,MVC、MVP、MVVM對比
1、MVC
MVC (Model-View-Controller, 模型-視圖-控制器),它是一種軟件的設計典范,它通過“業務邏輯、數據、界面顯示”的分離手段來組織代碼。MVC框架模式最早由Trygve Reenskaug 于1978年在Smalltalk-80系統上首次提出,對于MVC,Reenskaug也給出了自己的介紹:
Model: Model可以是一個獨立的對象,也可以是一系列對象的集合體;
View: View是Model中一些重要數據在視覺上的體現;
Controller: Controller用于連接User和System,比如當Controller接受到用戶的輸出時,會將其轉換成合適的事件消息,并將該事件消息傳遞給一個或多個View;
MVC作為最早且應用最廣泛的架構模式,其并沒有一個明確的定義,網上流傳的 MVC 架構圖也是形態各異,個人認為比較經典的MVC結構如下圖:
模型層(Model):
程序需要操作的數據或信息(系統中的業務邏輯部分),比如數據的存儲、網絡請求等,同時Model與View也存在一定的耦合,通過某種事件機制(如觀察者模式)通知View狀態改變或更新;Model還會接收來自Controller的事件,Model也會允許View 查詢相關數據以顯示自身狀態.
視圖層(View):
直接面向于最終用戶的視圖層,并提供給用戶操作界面,是Model的具體表現形式,是程序的外殼。該層只負責展示數據,主要是由各種GUI組件組成,同時響應用戶的交互行為并觸發Controller的邏輯,View還會通過在Model中注冊事件監聽Model的改變以此來刷新自身并展示給用戶(如監聽Model中的Bitmap圖像,當Bitmap加載或清除時,對應的View顯示不同的圖像).
控制器層(Controller):
Controller由View根據用戶行為觸發,并響應來自View的交互,然后根據View的事件邏輯修改對應的Model,Controller并不關心View如何展示數據或狀態,而是通過修改Model并由Model的事件機制來觸發View的刷新.
MVC優缺點及適用場景
Activity并非標準的Controller,它一方面用來控制了布局,另一方面還要在Activity中寫業務代碼,造成了Activity既像View又像Controller。
在Android開發中,就是指直接使用Activity并在其中寫業務邏輯的開發方式。顯然,一方面Activity本身就是一個視圖,另一方面又要負責處理業務邏輯,因此邏輯會比較混亂。
1.1 優點:
1、耦合性低,MVC本質是分層解耦,將表現層與表現邏輯很好的分離,減少模塊代碼之間的相互影響.
2、可擴展性好。由于耦合性低,添加需求,擴展代碼就可以減少修改之前的代碼,降低bug的出現率.
3、模塊職責劃分明確。主要劃分層M,V,C三個模塊,利于代碼的維護.
1.2 缺點:
View層和Model層是相互可知的,這意味著兩層之間存在耦合;
在Android開發中,xml作為View層,控制能力實在太弱了,只能把代碼寫在Activity中,造成了Activity既是Controller層,又是view層這樣一個窘境,而且Activity并不是一個標準的MVC模式中的Controller,它的首要職責是加載應用的布局和初始化用戶界面,并接受和處理來自用戶的操作請求,進而作出響應。隨著應用內界面及其邏輯的復雜度不斷提升,Activity類的職責不斷增加,以致變得龐大而臃腫。
1.3 適用場景:
適用于功能較少、業務邏輯簡單、界面不復雜的小型項目。
2、MVP
MVP全稱Model View Presenter,它是MVC的一個演化版本,是目前APP采用的主流架構設計模式,常用MVP結構如下圖:
MVP幾個主要部分如下:
模型層 (Model):主要提供數據存取功能。Presenter需要通過Model層存取、獲取數據,Model就像一個數據倉庫,更直白說Model是封裝了數據、網絡獲取數據的角色,或者是兩種數據獲取方式的集合。
視圖層 (View):處理用戶事件和視圖。在Android中,可能是指Activity、Fragment或者View。它含有一個Presenter成員變量。通常View需要實現一個邏輯接口,將View上的操作轉交給Presenter進行實現,最后,Presenter將調用View邏輯接口將結果返回給View元素。
協調者 (Presenter):負責通過Model存取書數據,連接View和Model,從Model中取出數據交給View。Presenter作為View和Model的橋梁,它從Model層檢索數據后,返回給View,使得View和Model之間沒有耦合,也將業務邏輯從View層上抽離出來。
所以,對于MVP的架構設計,我們有以下幾點需要說明:
這里的Model是用來存取數據的,也就是用來從指定的數據源中獲取數據,不要將其理解成MVC中的Model。在MVC中Model是數據模型,在MVP中,我們用Bean來表示數據模型。
Model和View不會直接發生關系,它們需要通過Presenter來進行交互。在實際的開發中,我們可以用接口來定義一些規范,然后讓我們的View和Model實現它們,并借助Presenter進行交互即可。
2.1 優點:
1、復雜的邏輯處理放在presenter進行處理,減少了activity和fragment的臃腫;
2、M層與V層完全分離,修改V層不會影響M層,降低了耦合性;
3、P層與V層的交互是通過接口來進行的,便于單元測試。
2.2 缺點:
1、由于對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過于頻繁,視圖需要改變,一般presenter也需要跟著改變;
2、隨著邏輯處理的增加,Presenter也會隨之變得越來越復雜,代碼臃腫的問題其實還是會出現;
3、由于MVP通過接口進行交互的,接口粒度不好設計和控制。粒度太小,就會存在大量接口,粒度太大,解耦效果不好;
2.3適用場景:
適用于界面復雜、復用性高、功能中等、業務邏輯中等或是簡單的項目。實際上MVP的思想很適合用于復雜的界面上,我們完全可以在項目中某一部分上使用MVP的思想去靈活實現
3、MVVM
MVVM全稱Model View ViewModel,你可以把MVP看做是MVVM的一個改進版本,常用的MVVM結構如下圖:
Model:
Model主要是封裝數據存儲或操作的一些邏輯,還會提供一系列的實體類用于UI綁定,ViewModel 則會在修改這些數據后將數據改變告訴View層并使UI更新。
View:
View用于處理界面的邏輯且不參與業務邏輯相關的操作,只負責顯示由ViewModel提供的數據,View層不做任何業務邏輯、不涉及操作數據、不處理數據,UI和數據嚴格的分開,對應于Activity和XML。
ViewModel:
ViewModel層做的事情剛好和View層相反,ViewModel只做和業務邏輯和業務數據相關的事,不做任何和UI相關的事情,ViewModel層不會持有任何控件的引用,更不會在ViewModel中通過UI控件的引用去做更新UI的事情。ViewModel就是專注于業務的邏輯處理,做的事情也都只是對數據的操作(這些數據綁定在相應的控件上會自動去更改UI)。同時DataBinding框架已經支持雙向綁定,讓我們可以通過雙向綁定獲取View層反饋給ViewModel層的數據,并對這些數據進行操作。
MVVM是通過數據來驅動界面的改變的,傳統的數據更新操作是通過持有View層的控件,當Modle層數據發生改變時,主動去更新View層的頁面,而這種架構則通過數據驅動來自動完成的,數據變化后會自動更新UI,UI的改變也能自動反饋到數據層,數據 成為主導因素。這樣MVVM層在業務邏輯處理中只要關心數據,不需要直接和UI打交道,在業務處理過程中簡單方便很多。ViewModel不涉及任何和UI相關的事,也不持有UI控件的引用。即便是控件改變了(比如:TextView換成EditText),ViewModel也幾乎不需要更改任何代碼。所以非常完美的解耦了View層和ViewModel,解決了上面我們所說的MVP的痛點。
3.1 優點:
1,低耦合,數據和業務邏輯處于一個獨立的ViewModel中,ViewModel只需要關注數據和業務邏輯,不需要和View層打交道。
2,可重用性,你可以把一些視圖邏輯放在一個ViewModel里面,讓很多view重用這段視圖邏輯。
3,可分開獨立開發,MVVM的分工是非常明顯的,由于View和ViewModel之間是松散耦合的:一個是處理業務和數據、一個是專門的UI處理。所以,完全可以由兩個人分工來做,一個做UI(XML和Activity)一個寫ViewModel,效率更高。
4,由于各層分工明確,極便于單元測試;
5,相對于MVP而言,MVVM不需要我們手動的處理大量的View和Model相關操作,也非常完美的解耦了View層和ViewModel;
3.2缺點:
1,數據綁定使得 Bug 很難被調試,你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了。
2,對于過大的項目,數據綁定需要花費更多的內存,而對與過于簡單的界面,使用MVVM無異是殺雞用牛刀,某種意義上來說,,數據綁定使得 MVVM 變得復雜和難用了。
3.3適用場景:
由于我本人也沒有在實際項目中使用過MVVM,所以具體哪種場景下使用更合適,我也不是很清楚。但是MVVM是不適用于簡單的界面和極度復雜的界面,在界面簡單的情況下,MVVM反而將我們的邏輯復雜化了,而界面元素過多,相對應的ViewModel的構建和維護成本就會變的很高,不利于項目的發展。
三,實際應用舉例(MVP)
由于MVC大家使用的比較多,且相對比較簡單,MVVM相對一般項目來說,使用場景不是很頻繁,并且本人也沒有在實際項目中使用過,MVP對大部分的項目來說,應用場景比較廣泛,并且使用也比較頻繁,所以接下來,我就以MVP的架構為例,簡單說下,如何來構建。主要講下我實現的思路,下面以一個簡單的獲取天氣的實例來講解。
步驟一:先根據業務要求,思考界面主要有哪些實現及展示效果,從而設計view層的接口
/**
* 定義界面的一些行為狀態
* Created by Administrator on 2018/4/2.
*/
public interface IgetWeatherView {
void showLoading();
void hideLoading();
void getWeatherFialed();//獲取天氣失敗
void getWeatherSuccess(WeatherBean.WeatherInfo weatherInfo);//獲取天氣成功
void displayWeatherInfo(WeatherBean.WeatherInfo weatherInfo);//展示天氣結果
}
步驟二:根據需要的獲取的數據,定義數據層的基本接口
public interface IWeahter {
void getWeatherInfo(Context mContext,String url, WeatherListener weatherListener);
/**
* 獲取天氣結果接口回調
*/
interface WeatherListener{
void getSuccess(WeatherBean.WeatherInfo weatherBean);
void getFailed();
}
}
步驟三:設置數據獲取的實現類(真實獲取數據的地方)
public class WeatherImpl implements IWeahter{
@Override
public void getWeatherInfo(Context mContext, String url, final WeatherListener weatherListener) {
VolleyRequestUtil.requestGet(mContext, url, "weather", WeatherBean.class, new VolleyListenerInterface(mContext,
VolleyListenerInterface.mListener,VolleyListenerInterface.mErrorListener) {
@Override
public void onSuccess(Object result) {
WeatherBean weatherBean = (WeatherBean) result;
weatherListener.getSuccess(weatherBean.weatherinfo);
}
@Override
public void onError(VolleyError error) {
weatherListener.getFailed();
}
});
}
}
步驟四:設計提供邏輯交互的presenter層,該層必須持有view和model層的引用,然后在邏輯交互過程中把兩者進行關聯。
public class WeatherPresenter {
private IWeahter iweather;
private IgetWeatherView getWeatherView;
public WeatherPresenter (IgetWeatherView getWeatherView){
this.getWeatherView= getWeatherView;
iweather= new WeatherImpl();
}
/**
* 獲取天氣信息
* @param context
*/
public void getWeatherInfo(Context context){
getWeatherView.showLoading();
iweather.getWeatherInfo(context, "http://www.weather.com.cn/data/sk/101010100.html", new IgetWeatherView .WeatherListener() {
@Override
public void getSuccess(WeatherBean.WeatherInfo weatherInfo) {
getWeatherView.hideLoading();
getWeatherView.getWeatherSuccess(weatherInfo);
getWeatherView.displayWeatherInfo(weatherInfo);
}
@Override
public void getFailed() {
getWeatherView.hideLoading();
getWeatherView.getWeatherFialed();
}
});
}
步驟五,view層的實現(Activity)。有了presenter,activity的具體實現只要實現view層的基本接口即可,剩下的只是利用presenter做簡單的界面實現
public class MainActivity extends FragmentActivity implements IgetWeatherView{
private Button request_btn;
private TextView display_weather_tv;
private WeatherPresenter weahterPresenter;
private ProgressBar progressbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
display_weather_tv = (TextView) findViewById(R.id.display_weather_tv);
request_btn= (Button) findViewById(R.id.request_btn);
progressbar= (ProgressBar)findViewById(R.id.progressbar);
weahterPresenter= new WeatherPresenter(this);
}
private void initListener() {
request_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
weahterPresenter.getWeatherInfo(MainActivity.this);
}
});
}
@Override
public void showLoading() {
progressbar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressbar.setVisibility(View.GONE);
}
@Override
public void getWeatherFialed() {
Toast.makeText(this,"獲取天氣信息失敗",Toast.LENGTH_SHORT).show();
}
@Override
public void getWeatherSuccess(WeatherBean.WeatherInfo weatherInfo) {
Log.d("TAG", "city is " + weatherInfo.getCity());
Log.d("TAG", "temp is " + weatherInfo.getTemp());
Log.d("TAG", "time is " + weatherInfo.getTime());
}
@Override
public void displayWeatherInfo( WeatherBean.WeatherInfo weatherInfo) {
display_weather_tv.setText("city is "+weatherInfo.getCity() +
"temp is "+weatherInfo.getTemp() +
"time is "+weatherInfo.getTime());
}
總結:
上面我們總結了三種架構模式的特點及優缺點,并重點對MVP的模式做了實例講解。真正要理解各種架構模式必須要自己動手實踐才行。
上述例子中只貼了核心代碼,其中網絡是自己分裝的volley請求。使用MVP模式后發現,代碼量和類確實變多了,不過回過頭來,看activity的代碼,確實簡潔了很多。可能這個例子還不是很明顯,不過對于項目比較大,界面邏輯比較多的情況下,使用MVP的模式的確能放大其優勢。不過,最終,在自己的項目中該使用什么模式,還是取決于具體項目大小以及復雜程度,還有團隊的協作情況。總之,沒有最好的模式,只有最適合團隊的模式。不過,作為一名android開發者,每一種設計模式都應該了解,這樣在將來要用到的時候才能隨心所欲的使用,不至于犯怵。好了,關于Android架構模式就總結到這里,大家有什么疑問的,歡迎一起交流學習。