Android架構設計之MVC、MVP、MVVM淺談

一,前言:

關于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結構如下圖:


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結構圖

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結構如下圖:

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架構模式就總結到這里,大家有什么疑問的,歡迎一起交流學習。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容