Android MVC、MVP和MVVP的概念、運用及區別
少年不識愁滋味,愛上層樓。愛上層樓,為賦新詞強說愁。
而今識盡愁滋味,欲說還休。欲說還休,卻道天涼好個秋。
一首辛棄疾的《醜奴兒·書博山道中壁》送給大家
概述
MVC、MVP和MVVM都是為了解決介面呈現和邏輯程式碼分離而出現的模式。經典的MVC模式是M-V-X模式的老祖宗,MVP和MVVM都是在MVC的基礎上演化而來。本文分為三個部分:
- 概述MVC、MVP和MVVM的概念、區別、以及適用場景。
- 用Demo演示MVP及MVVM的使用
- Demo原始碼下載
概述MVC、MVP和MVVM的概念、區別、以及適用場景。
簡述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
- 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
- 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的程式碼示例
工程大綱如下:
預覽大綱可以發現只有一個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資訊如下:
MVVM程式碼示例
工程大綱如下:
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框架。
參考: