Android MVC、MVP和MVVP的概念、運用及區別

少年不識愁滋味,愛上層樓。愛上層樓,爲賦新詞強說愁。

而今識盡愁滋味,欲說還休。欲說還休,卻道天涼好個秋。

一首辛棄疾的《醜奴兒·書博山道中壁》送給大家

概述

MVC、MVP和MVVM都是爲了解決界面呈現和邏輯代碼分離而出現的模式。經典的MVC模式是M-V-X模式的老祖宗,MVP和MVVM都是在MVC的基礎上演化而來。本文分爲三個部分:

  1. 概述MVC、MVP和MVVM的概念、區別、以及適用場景。
  2. 用Demo演示MVP及MVVM的使用
  3. Demo源碼下載

概述MVC、MVP和MVVM的概念、區別、以及適用場景。

簡述MVC

mvc模型圖

  • M-Model : 業務邏輯和實體模型(biz/bean)
  • V-View : 佈局文件(XML)
  • C-Controllor : 控制器(Activity)

MVC雖然將界面呈現和邏輯代碼分離了,但是在實際的Android開發中並沒有完全起到想要的作用。View對應的XML文件實際能做的事情很少,很多界面顯示由Controllor對應的Activity給做了,這樣使得Activity變成了一個類似View和Controllor之間的一個東西。如果是小型項目,MVC是沒任何問題的。因爲項目比較小嘛,開發週期比較短,Controllor臃腫點也可以理解。假設項目越來越大,尤其是再加上比較複雜的邏輯,這時候一個Activity幾千行代碼就比較蛋疼了,再加點迷之縮進,那酸爽~~嘖嘖。所以MVC比較適用於快速開發的小型項目。

簡述MVP

mvp模型圖

  • M-Model : 業務邏輯和實體模型(biz/bean)
  • V-View : 佈局文件(XML)和Activity
  • P-Presenter : 完成View和Model的交互

儘管MVC設計的非常nice,但代碼臃腫的問題仍然沒有得到很好的解決,這個時候MVP就要登場了。可以看到MVP相對於MVC改動是非常大的。Activity直接當做View使用,代替MVC中C的是P-Presenter。對比MVC和MVP的模型圖可以發現變化最大的是View和Model不在直接通信,所有交互的工作都通過Presenter來解決。既然兩者都通過Presenter來通信,爲了複用和可拓展性,MVP模式基於接口設計也就很好理解了。兩者都通過Presenter來通信,好很多的好處,例如提高代碼複用性啦、增加可拓展性啦、降低耦合度啦、代碼邏輯更加清晰啦。但是、本來兩個能直接通信的東西現在要通過第三方來通信,那勢必會增加很多類。沒錯,MVP模式雖然很好,但是增加了很多的接口和實現類。代碼邏輯雖然清晰,但是代碼量要龐大一些。當剛接手一個爛尾的MVP模式,如果事先沒了解過MVP,會不會一臉的懵逼。所以MVP比較適用於中小型的項目,大型項目慎用。

簡述MVVM

mvvm模型圖

  • M-Model : 實體模型(biz/bean)
  • V-View : 佈局文件(XML)
  • VM-ViewModel : binder所在之處,對外暴露出公共屬性,View和Model的綁定器

有的讀者該說了,你作用這不是和MVC一樣嘛!是的,對應的文件看起來確實是一樣的,但是作用不同。MVVM和MVP一樣,View和Model不允許直接交互。只能通過ViewModel。MVVM神奇的地方在於通過ViewModel隔離了UI層和業務邏輯層,降低程序的耦合度。而且,佈局文件裏可以進行視圖邏輯!並且Model發生變化,View也隨着發生變化。佈局文件里居然還能寫邏輯,斯國一!

Demo演示MVP及MVVM的使用

MVP的代碼示例

工程大綱如下:

mvpDemo

預覽大綱可以發現只有一個Bean:User。業務邏輯只有一個GetUserInfo。先放下View層和Presenter層。我們來看下最簡單的Bean層和Biz層(業務邏輯)。

public class User {
    private int id;
    private String account;
    private String pwd;

    getter()/setter()...
}

public interface IGetUserInfo {

    void getUserInfo(int id, OnUserInfoListener listener);
}

public class GetUserInfoImpl implements IGetUserInfo{

    @Override
    public void getUserInfo(final int id, final OnUserInfoListener listener) {
        // 模擬數據
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (id == 666){
                    User user = new User();
                    user.setId(666);
                    user.setAccount("一口仨饃");
                    user.setPwd("走在勇往直前的路上");
                    listener.getUserInfoSuccess(user);
                }else{
                    String msg = "損色!獲取數據失敗啦";
                    listener.getUserInfoFailure(msg);
                }
            }
        }).start();
    }
}

public interface OnUserInfoListener {

    void getUserInfoSuccess(User user);

    void getUserInfoFailure(String msg);
}

爲了觀賞性,省略了部分代碼,如有需要可以在第三部分下載源碼。這裏我們定義了兩個接口和實現類。MVP模式的接口之多由此可見一斑。下面一一解釋各個類的作用

  • User:Bean也可以叫做POJO,純淨的類。
  • IGetUserInfo:獲取用戶信息的接口
  • GetUserInfoImpl:獲取用戶信息的實現類
  • OnUserInfoListener:獲取用戶信息的監聽接口

這裏我們主要解析下GetUserInfoImpl這個類,GetUserInfoImpl是接口IGetUserInfo的具體實現類。複寫了IGetUserInfo#getUserInfo()方法。在GetUserInfoImpl的getUserInfo()方法中,模擬後臺請求數據。如果成功請求到User信息,則調用接口OnUserInfoListener#getUserInfoSuccess(User user)方法。否則調用OnUserInfoListener#getUserInfoFailure(String msg)方法。

Bean和Biz層的業務邏輯都有了,下面該通知View顯示數據。由於View個Model在Presenter中使用接口通信,所以先定義一個用於顯示數據的接口。然後在要獲取數據的Activity中實現此接口,並複寫其中所有的抽象方法。

 public interface IUserInfoShow {

    void beforeLoding();

    void getUserInfoSucceed(User user);

    void getUserInfoFailed(String msg);

    void afterLoading();
}

// MainActivity extends Activity implements IUserInfoShow 

View層的接口定義完成之後,就剩最後的大Boss-Presenter登場了!

package com.dyk.mvp.presenter;  

import com.dyk.mvp.bean.User;
import com.dyk.mvp.biz.IGetUserInfo;
import com.dyk.mvp.biz.OnUserInfoListener;
import com.dyk.mvp.view.IUserInfoShow;

/**
 * Created by dyk on 2016/4/14.
 */
public class UserInfoPresenter {
    private IGetUserInfo mIGetUserInfo;
    private IUserInfoShow mUserInfoShow;

    public UserInfoPresenter(IUserInfoShow mUserInfoShow, IGetUserInfo mIGetUserInfo) {
        this.mUserInfoShow = mUserInfoShow;
        this.mIGetUserInfo = mIGetUserInfo;
    }

    public void getUserInfo(int id){
        mUserInfoShow.beforeLoding();
        mIGetUserInfo.getUserInfo(id, new OnUserInfoListener() {
            @Override
            public void getUserInfoSuccess(User user) {
                mUserInfoShow.getUserInfoSucceed(user);
                mUserInfoShow.afterLoading();
            }

            @Override
            public void getUserInfoFailure(String msg) {
                mUserInfoShow.getUserInfoFailed(msg);
                mUserInfoShow.afterLoading();
            }
        });
    }
}

在關鍵的Presenter中,我們定義了一個兩個參數的構造方法和一個getUserInfo()方法。

在構造方法中,傳進來的兩個參數分別是:IUserInfoShow、IGetUserInfo。前者是View層的頂級接口,後者是M層的頂級接口。並且賦給屬性mUserInfoShow、mIGetUserInfo。看來今天所有的工作全看他們倆的了!

在getUserInfo()方法中,傳進來的參數時id,這個id就是用戶的id。在方法的開始調用mUserInfoShow.beforeLoding(),這意味着我們可以在實現了IUserInfoShow類的beforeLoding()方法中做一些預處理,比如展示Loading動畫等等,緊接着調用了mIGetUserInfo.getUserInfo(),並且new了一個OnUserInfoListener的匿名內部類。也就是我們獲取用戶信息的監聽。接下來就很簡單了,如果成功則調用mUserInfoShow.getUserInfoSucceed(user);mUserInfoShow.afterLoading();失敗的話調用mUserInfoShow.getUserInfoFailed(msg);mUserInfoShow.afterLoading();。一個簡單的Presenter就完成了,剩餘的就看IUserInfoShow的實現類

package com.dyk.mvp;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import com.dyk.mvp.bean.User;
import com.dyk.mvp.biz.GetUserInfoImpl;
import com.dyk.mvp.presenter.UserInfoPresenter;
import com.dyk.mvp.view.IUserInfoShow;

public class MainActivity extends Activity implements IUserInfoShow {
    private static final String TAG = "MVP";

    private UserInfoPresenter mUserInfoPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mUserInfoPresenter = new UserInfoPresenter(this, new GetUserInfoImpl());
        mUserInfoPresenter.getUserInfo(666);
    }

    @Override
    public void beforeLoding() {
        Log.i(TAG, "beforeLoding");
    }

    @Override
    public void getUserInfoSucceed(User user) {
        Log.i(TAG, "id:"+user.getId()+" account:"+user.getAccount()+" pwd:"+user.getPwd());
    }

    @Override
    public void getUserInfoFailed(String msg) {
        Log.i(TAG, "msg=" + msg);
    }

    @Override
    public void afterLoading() {
        Log.i(TAG, "afterLoading");
    }
}

在MainActivity#OnCreate()裏,首先實例化一個UserInfoPresenter對象,並將GetUserInfoImpl作爲第二個參數傳入。然後只需執行一行代碼mUserInfoPresenter.getUserInfo(666);即可。Activity的代碼是不是看起來簡潔的多。Log信息如下:
mvpLog

MVVM代碼示例

工程大綱如下:

mvvpDemo

WTF!居然只有Activity、Bean以及一個activity_main.xml?是的,MVVP就是這麼簡潔。可是簡潔不意味着簡單。Google去年I/O大會發布了一款MVVP的框架,data binding。今天以此框架爲例。
在Module的build.gradle中添加

dataBinding {
    enabled true
}

代碼很少,直接貼上來了。後面會仔細解釋。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="com.dyk.mvvp.bean.User" />

        <variable
            name="user"
            type="User" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.id)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.account}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.pwd}" />

    </LinearLayout>

</layout>

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setUser(new User(666,"一口仨饃", "走在勇往直前的路上"));
    }
}

Run一下試試看。區區幾行代碼就搞定了User信息的綁定與展示首先看佈局文件。最外層爲layout。其次定義了data元素,裏面有個變量(variable)名稱爲user,類型爲com.dyk.mvvp.bean.User中的User,也就是我們的Bean。然後在TextView.setText()中直接使用user.account屬性就可以獲取到user的屬性,無論是否私有。Java基本類型不用使用import導包,另外還支持Java語法。可以看到id屬性使用了String.value()方法轉換爲String。然後再MainActivity中沒有直接setContentView()而是調用DataBindingUtil.setContentView(this, R.layout.activity_main);返回的是個繼承ViewDataBinding的泛型。這裏是ActivityMainBinding,注意下,這個返回泛型名稱是有規則的:佈局文件去掉下劃線,後面第一個字母大寫,再加上Binding。例如這裏的佈局文件爲activity_main,對應的泛型爲:ActivityMainBinding,這裏只是一個示例。感興趣的同學可以去仔細研究data binding框架。

源碼下載 : http://download.csdn.net/detail/qq_17250009/9492043

參考:

http://blog.csdn.net/loongggdroid/article/details/50592777
http://blog.csdn.net/lmj623565791/article/details/46596109
http://rocko.xyz/2015/02/06/Android%E4%B8%AD%E7%9A%84MVP/
http://rocko.xyz/2015/11/07/MVVM_Android-CleanArchitecture/
http://blog.csdn.net/johnny901114/article/details/50706329
http://blog.csdn.net/qibin0506/article/details/47393725
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1110/3669.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章