MVC、MVP、MVVM是咱們工做和麪試中都比較重要的一塊,但不少時候咱們卻有點迷惑。好比看了好多篇文章都搞不懂MVC究竟是個啥原本想寫個MVP寫着寫着就變成MVC了,到底Databing和MVVM之間有啥見不得人的關係。本篇文章主要從發展的角度來介紹,如mvp,mvvm的出現都是爲了解決前者的哪些問題。若是你有一樣的疑問,本篇文章可能會給你帶來一點收穫。可是架構和設計模式相對來講不是那麼容易捉摸透的東西,不少須要通過實踐才能體會,另外因爲本人水平有限,若是寫的不對或者不嚴謹的地方,請不要打我。html
可能因爲MVP、MVVM的興起,MVC在android中的應用變得愈來愈少了,但MVC是基礎,理解好MVC才能更好的理解MVP,MVVM。由於後兩種都是基於MVC發展而來的。java
咱們從網上搜索mvc相關資料時,若是你多看幾篇文章的話可能會發現,好像他們介紹的設計圖都不太同樣,這裏羅列了大部分的設計圖android
到底上面列出的設計圖哪一個纔是對的。其實都是對的。爲何這麼說呢,這得從mvc的發展提及。面試
MVC框架模式最先由Trygve Reenskaug 於1978年在Smalltalk-80系統上首次提出。通過了這麼多年的發展,固然會演變出不一樣的版本,但核心沒變依舊仍是三層模型Model-View-Control。數據庫
箭頭→表明的是一種事件流向,並不必定要持有對方,好比上圖中model→view的事件流向,view能夠經過註冊監聽器的形式獲得model發來的事件。在設計中model view controller之間若是要通信,儘可能設計成不直接持有,這樣方便複用,也符合mvc的設計初衷。在Android中三者對應的關係以下:後端
視圖層(View)對應於xml佈局文件和java代碼動態view部分設計模式
控制層(Controller)MVC中Android的控制層是由Activity來承擔的,Activity原本主要是做爲初始化頁面,展現數據的操做,可是由於XML視圖功能太弱,因此Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。緩存
模型層(Model)針對業務模型,創建的數據結構和相關的類,它主要負責網絡請求,數據庫處理,I/O的操做。bash
因爲android中有個god object的存在activity,再加上android中xml佈局的功能性太弱,因此activity承擔了絕大部分的工做。因此在android中mvc更像是這種形式:網絡
由於activity扮演了controller和view的工做,因此controller和view不太好完全解耦,可是在必定程度上咱們仍是能夠解耦的。
Talk is cheap. Show me the code. 扯了這麼多,咱們來看點代碼。
經過代碼來看下,mvc在Android中的實現
結構很簡單,這裏介紹下其中的關鍵代碼
public interface BaseModel {
void onDestroy();
}複製代碼
BaseModel顧名思義就是全部業務邏輯model的父類,這裏的onDestroy()方法用於跟activity或者fragment生命週期同步,在destroy作一些銷燬操做
public interface Callback1<T> {
void onCallBack(T t);
}
public interface Callback2<T,P> {
void onCallBack(T t,P p);
}複製代碼
Callback是根據View或者Controller調用Model時回調的參數個數選擇使用
public class SampleModel implements BaseModel{
public void getUserInfo(String uid,Callback1<UserInfo> callback)
{
UserInfo userInfo= new HttpUtil<UserInfo>().get(uid);
callback.onCallBack(userInfo);
}
@Override
public void onDestroy() {
}
public class UserInfo
{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}複製代碼
SampleModel是咱們業務邏輯的具體實現
public class SampleActivity extends AppCompatActivity {
private SampleModel sampleModel;
Button button;
EditText textView;
TextView tvAge,tvName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
sampleModel=new SampleModel();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getUserInfo(textView.getText().toString());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
sampleModel.onDestroy();
}
/**
* 獲取用戶信息
* @param uid
*/
private void getUserInfo(String uid)
{
sampleModel.getUserInfo(uid, new Callback1<SampleModel.UserInfo>() {
@Override
public void onCallBack(SampleModel.UserInfo userInfo) {
setDataToView(userInfo);
}
});
}
/**
* 設置用戶信息到view
*/
private void setDataToView(SampleModel.UserInfo userInfo)
{
tvAge.setText(userInfo.getAge());
tvName.setText(userInfo.getName());
}
}複製代碼
前面說了Activity充當View和Controller,可是咱們依然要區分到底哪一部分是View的操做,哪一部分是Controller的操做,咱們分析下事件的流向 。button點擊事件的觸發:View→Controller獲取用戶信息事件的觸發:Controller→Model綁定用戶信息到View:Controller→View至此MVC就講完了
咱們這裏根據sample來總結下:
MVP跟MVC很相像,文章開頭列出了不少種MVC的設計圖,因此根據MVC的發展來看,咱們把MVP當成MVC來看也不爲過,由於MVP也是三層,惟一的差異是Model和View之間不進行通信,都是經過Presenter完成。
前面介紹MVC的時候提到了算是致命缺點吧,在android中因爲activity(god object)的存在,Controller和View很難作到徹底解耦,但在MVP中就能夠很好的解決這個問題。看下MVP的設計圖:
通常狀況下就這兩種。
依然延續MVC的例子,修改下結構經過MVP去實現,看下項目代碼結構:
callback,http包下內容基本一致,主要看下不一樣的地方
public interface BasePresenter {
void onDestroy();
}複製代碼
BasePresenter相似於MVC中的BaseModel,主要負責業務邏輯的實現。咱們這裏沒有把業務邏輯放在Model裏去實現,固然把主要業務邏輯放在Model中去實現也是能夠的。google的MVP實現方案是把業務邏輯放在presenter中,弱化Model,咱們這裏也是這樣作的。
public interface BaseView<P extends BasePresenter> {
void setPresenter(P presenter);
}複製代碼
BaseView是全部View的父類,將android中的view抽象話出來,只有跟view相關的操做都由baseView的實現類去完成。
public class SampleContract {
public static class Presenter implements BasePresenter
{
public void getUserInfo(String uid,Callback1<SampleModel.UserInfo> callback)
{
SampleModel.UserInfo userInfo= new HttpUtil<SampleModel.UserInfo>().get(uid);
callback.onCallBack(userInfo);
}
@Override
public void onDestroy() {
}
}
public interface View extends BaseView<Presenter>
{
void setDataToView(SampleModel.UserInfo userInfo);
}
}複製代碼
Contract 契約類這是Google MVP與其餘實現方式的又一個不一樣,契約類用於定義同一個界面的view的接口和presenter的具體實現。好處是經過規範的方法命名和註釋能夠清晰的看到整個頁面的邏輯。
public class SampleActivity extends AppCompatActivity implements SampleContract.View{
private SampleContract.Presenter mPresenter;
Button button;
EditText textView;
TextView tvAge,tvName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
setPresenter(new SampleContract.Presenter());
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.getUserInfo(textView.getText().toString(), new Callback1<SampleModel.UserInfo>() {
@Override
public void onCallBack(SampleModel.UserInfo userInfo) {
setDataToView(userInfo);
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.onDestroy();
}
@Override
public void setDataToView(SampleModel.UserInfo userInfo) {
tvAge.setText(userInfo.getAge());
tvName.setText(userInfo.getName());
}
@Override
public void setPresenter(SampleContract.Presenter presenter) {
mPresenter=presenter;
}
}複製代碼
這裏的SampleActivity實現了SampleContract.View只是做爲View存在的。雖然看起來,跟MVC中的實現很類似,但卻有本質的區別。mPresenter爲Model和View之間交互的橋樑。Presenter跟View相互持有,這裏SampleActivity實現了SampleContract.View,mPresenter做爲SampleActivity的成員變量,SampleActivity固然持有mPresenter,因爲mPresenter是非靜態的成員標量,所以默認持有SampleActivity的引用。
經過引入接口BaseView,讓相應的視圖組件如Activity,Fragment去實現BaseView,實現了視圖層的獨立,經過中間層Preseter實現了Model和View的徹底解耦。MVP完全解決了MVC中View和Controller傻傻分不清楚的問題,可是隨着業務邏輯的增長,一個頁面可能會很是複雜,UI的改變是很是多,會有很是多的case,這樣就會形成View的接口會很龐大。
MVP中咱們說過隨着業務邏輯的增長,UI的改變多的狀況下,會有很是多的跟UI相關的case,這樣就會形成View的接口會很龐大。而MVVM就解決了這個問題,經過雙向綁定的機制,實現數據和UI內容,只要想改其中一方,另外一方都可以及時更新的一種設計理念,這樣就省去了不少在View層中寫不少case的狀況,只須要改變數據就行。先看下MVVM設計圖:
通常狀況下就這兩種狀況,這看起來跟MVP好像沒啥差異,其實區別仍是挺大的,在MVP中View和presenter要相互持有,方便調用對方,而在MVP中 View和ViewModel經過Binding進行關聯,他們以前的關聯處理經過DataBinding完成。
一句話表述就是,MVVM是一種思想,DataBinding是谷歌推出的方便實現MVVM的工具。在google推出DataBinding以前,由於xml layout功能較弱,想實現MVVM很是困難。而DataBinding的出現可讓咱們很方便的實現MVVM。
DataBinding是實現視圖和數據雙向綁定的工具,這裏簡單介紹下基本用法,詳細用法能夠參照官方:https://developer.android.com/topic/libraries/data-binding/啓用DataBinding,只須要在gradle文件中添加以下代碼:
android {
dataBinding{
enabled true
}
}複製代碼
經過DataBindingUtil能夠動態生成一個ViewDataBinding的子類,類名以layout文件名大寫加Binding組成,如:
ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);複製代碼
在layout中須要咱們配置,每一個控件綁定的實體對象,以layout進行包裹,data中配置變量名和類型,經過@{}或@={}的方式進行引用,其中@={}的方式表示雙向綁定。目前支持雙向綁定的控件以下:
AbsListView android:selectedItemPositionCalendarView android:dateCompoundButton android:checkedDatePicker android:year, android:month, android:dayNumberPicker android:valueRadioGroup android:checkedButtonRatingBar android:ratingSeekBar android:progressTabHost android:currentTab TextView android:textTimePicker android:hour, android:minute
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data >
<variable
name="user"
type="com.androfarmer.mvvm.model.SampleModel.UserInfo">
</variable>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.name}"
/>
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.age+``}"
/>
</LinearLayout>
</layout>複製代碼
以上爲具體在xml的用法展現
public static class UserInfo extends BaseObservable
{
private int age;
private String name;
@Bindable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(BR.age);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}複製代碼
爲了實現雙向綁定還須要對數據實體類作處理,繼承BaseObservable,對讀寫方法作@Bindable和notifyPropertyChanged處理。還能夠直接使用官方提供的泛型可觀察對象 ObservableField,好比:
private ObservableField name=new ObservableField<>()
MVVM中跟MVP中同樣,將三層劃分的很清楚,Activity和xml layout充當View,ViewModel處理業務邏輯以及獲取數據,弱化Model。不少代碼跟前面相似,這裏只列出核心代碼,ViewModel層的:
public interface BaseViewModel {
void onDestroy();
}
public abstract class AbstractViewModel<T extends ViewDataBinding> implements BaseViewModel {
public T mViewDataBinding;
public AbstractViewModel(T viewDataBinding)
{
this.mViewDataBinding=viewDataBinding;
}
@Override
public void onDestroy() {
mViewDataBinding.unbind();
}
}
public class SampleViewModel extends AbstractViewModel<ActivitySampleMvvmBinding> {
public SampleViewModel(ActivitySampleMvvmBinding viewDataBinding) {
super(viewDataBinding);
}
public void getUserInfo(String uid, Callback1<SampleModel.UserInfo> callback)
{
//從網絡或緩存獲取信息
SampleModel.UserInfo userInfo=new SampleModel.UserInfo();
userInfo.setName("tom");
userInfo.setAge(18);
callback.onCallBack(userInfo);
}
}複製代碼
ViewMode層主要處理業務邏輯和獲取數據,mViewDataBinding是經過View層傳遞過來。
private SampleViewModel mSampleViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);
mSampleViewModel=new SampleViewModel(binding);
setDataToView();
}
private void setDataToView()
{
mSampleViewModel.getUserInfo("uid", new Callback1<SampleModel.UserInfo>() {
@Override
public void onCallBack(SampleModel.UserInfo userInfo) {
mSampleViewModel.mViewDataBinding.setUser(userInfo);
}
});
}複製代碼
xml layout代碼在上面介紹databing的用法時已給出,經過上面代碼咱們就將數據UserInfo跟View進行綁定了。好比咱們更新用戶信息,能夠直接對View上的屬性進行修改:mSampleViewModel.mViewDataBinding.tvName.setText("rose");也能夠經過修改UserInfo實體類的字段信息:mSampleViewModel.mViewDataBinding.setUser(userInfo);
今後告別MVP中View層好多接口的問題,讓View變得更簡潔,修改任何一方,二者都會保持數據同步。
看起來MVVM很好的解決了MVC和MVP的不足,可是因爲數據和視圖的雙向綁定,致使出現問題時不太好定位來源,有可能數據問題致使,也有可能業務邏輯中對視圖屬性的修改致使。若是項目中打算用MVVM的話能夠考慮使用官方的架構組件ViewModel、LiveData、DataBinding去實現MVVM
前面在介紹MVC、MVP、MVVM時並無去詳細列出他們的優缺點,主要緣由有兩個:
好比在mvp中咱們要實現根據業務邏輯和頁面邏輯作不少Present和View的具體實現,若是這些case太多,會致使代碼的可讀性變差。可是經過引入contract契約類,會讓業務邏輯變得清晰許多。所以無論是用哪一種設計模式,只要運用得當,均可以達到想要的結果。
若是非要說怎麼選的話,以我淺薄的知識建議以下:
PS:代碼部分不少只是爲了演示具體設計模式原理,部分爲僞代碼,還有些代碼寫的不是那麼嚴謹。
本篇文章參考以下:http://www.voidcn.com/article/p-ssodjasa-brk.htmlhttps://www.jianshu.com/p/4830912f5162
做者:任玉剛