MVC、MVP、MVVM,我到底該怎麼選? 本文由玉剛說寫作平臺提供寫作贊助
原作者:AndroFarmer
版權宣告:本文版權歸微信公眾號玉剛說所有,未經許可,不得以任何形式轉載
前言 MVC、MVP、MVVM是我們工作和麵試中都比較重要的一塊,但很多時候我們卻有點迷惑。比如看了好多篇文章都搞不懂MVC到底是個啥本來想寫個MVP寫著寫著就變成MVC了,到底Databing和MVVM之間有啥見不得人的關係。本篇文章主要從發展的角度來介紹,如mvp,mvvm的出現都是為了解決前者的哪些問題。如果你有同樣的疑問,本篇文章可能會給你帶來一點收穫。但是架構和設計模式相對來說不是那麼容易捉摸透的東西,很多需要經過實踐才能體會,另外由於本人水平有限,如果寫的不對或者不嚴謹的地方,請不要打我。
MVC 可能由於MVP、MVVM的興起,MVC在android中的應用變得越來越少了,但MVC是基礎,理解好MVC才能更好的理解MVP,MVVM。因為後兩種都是基於MVC發展而來的。
1、MVC眼花繚亂設計圖 我們從網上搜尋mvc相關資料時,如果你多看幾篇文章的話可能會發現,好像他們介紹的設計圖都不太一樣,這裡羅列了大部分的設計圖
mvc1.png mvc2.png mvc3.png mvc4.png 2、MVC設計圖解釋 到底上面列出的設計圖哪個才是對的。其實都是對的。為什麼這麼說呢,這得從mvc的發展說起。 MVC框架模式最早由Trygve Reenskaug 於1978年在Smalltalk-80系統上首次提出。經過了這麼多年的發展,當然會演變出不同的版本,但核心沒變依舊還是三層模型Model-View-Control。
3、MVC三層之間的關係 箭頭→代表的是一種事件流向,並不一定要持有對方,比如上圖中model→view的事件流向,view可以通過註冊監聽器的形式得到model發來的事件。在設計中model view controller之間如果要通訊,儘量設計成不直接持有,這樣方便複用。也符合mvc的設計初衷 在android中三者對應的關係如下:
檢視層(View) 對應於xml佈局檔案和java程式碼動態view部分
控制層(Controller) MVC中Android的控制層是由Activity來承擔的,Activity本來主要是作為初始化頁面,展示資料的操作,但是因為XML檢視功能太弱,所以Activity既要負責檢視的顯示又要加入控制邏輯,承擔的功能過多。
模型層(Model) 針對業務模型,建立的資料結構和相關的類,它主要負責網路請求,資料庫處理,I/O的操作。
由於android中有個god object的存在activity,再加上android中xml佈局的功能性太弱,所以activity承擔了絕大部分的工作。所以在android中mvc更像是這種形式:
因為activity扮演了controller和view的工作,所以controller和view不太好徹底解耦,但是在一定程度上我們還是可以解耦的。 Talk is cheap. Show me the code. 扯了這麼多,我們來看點程式碼。 4、MVC sample 通過程式碼來看下,mvc在android中的實現
結構很簡單,這裡介紹下其中的關鍵程式碼
public interface BaseModel { void onDestroy(); } 複製程式碼 BaseModel顧名思義就是所有業務邏輯model的父類,這裡的onDestroy()方法用於跟activity或者fragment生命週期同步,在destroy做一些銷燬操作
public interface Callback1 { 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就講完了
5、MVC總結 我們這裡根據sample來總結下:
具有一定的分層,model徹底解耦,controller和view並沒有解耦 層與層之間的互動儘量使用回撥或者去使用訊息機制去完成,儘量避免直接持有 controller和view在android中無法做到徹底分離,但在程式碼邏輯層面一定要分清 業務邏輯被放置在model層,能夠更好的複用和修改增加業務 MVP 1、MVP說明 MVP跟MVC很相像,文章開頭列出了很多種MVC的設計圖,所以根據MVC的發展來看,我們把MVP當成MVC來看也不為過,因為MVP也是三層,唯一的差別是Model和View之間不進行通訊,都是通過Presenter完成。 前面介紹MVC的時候提到了算是致命缺點吧,在android中由於activity(god object)的存在,Controller和View很難做到完全解耦。但在MVP中就可以很好的解決這個問題 看下MVP的設計圖:
mvp.png 一般情況下就這兩種 2、MVP Sample 依然延續MVC的例子,修改下結構通過MVP去實現,看下專案程式碼結構:
callback,http包下內容基本一致,主要看下不同的地方 public interface BasePresenter { void onDestroy(); } 複製程式碼 BasePresenter類似於MVC中的BaseModel,主要負責業務邏輯的實現。我們這裡沒有把業務邏輯放在Model裡去實現,當然把主要業務邏輯放在Model中去實現也是可以的。google的MVP實現方案是把業務邏輯放在presenter中,弱化Model,我們這裡也是這樣做的。
public interface BaseView
{ 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的引用。
3、MVP總結 通過引入介面BaseView,讓相應的檢視元件如Activity,Fragment去實現BaseView,實現了檢視層的獨立,通過中間層Preseter實現了Model和View的完全解耦。MVP徹底解決了MVC中View和Controller傻傻分不清楚的問題,但是隨著業務邏輯的增加,一個頁面可能會非常複雜,UI的改變是非常多,會有非常多的case,這樣就會造成View的介面會很龐大。
MVVM 1、MVVM說明 MVP中我們說過隨著業務邏輯的增加,UI的改變多的情況下,會有非常多的跟UI相關的case,這樣就會造成View的介面會很龐大。而MVVM就解決了這個問題,通過雙向繫結的機制,實現資料和UI內容,只要想改其中一方,另一方都能夠及時更新的一種設計理念,這樣就省去了很多在View層中寫很多case的情況,只需要改變資料就行。 先看下MVVM設計圖:
mvvm.png 一般情況下就這兩種情況,這看起來跟MVP好像沒啥差別,其實區別還是挺大的,在MVP中View和presenter要相互持有,方便呼叫對方,而在MVP中 View和ViewModel通過Binding進行關聯,他們之前的關聯處理通過DataBinding完成。 2、MVVM與DataBinding的關係 一句話表述就是,MVVM是一種思想,DataBinding是谷歌推出的方便實現MVVM的工具。在google推出DataBinding之前,因為xml layout功能較弱,想實現MVVM非常困難。而DataBinding的出現可以讓我們很方便的實現MVVM。
3、DataBinding簡介 DataBinding是實現檢視和資料雙向繫結的工具,這裡簡單介紹下基本用法,詳細用法可以參照官方:developer.android.com/topic/libra… 啟用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:selectedItemPosition CalendarView android:date CompoundButton android:checked DatePicker android:year, android:month, android:day NumberPicker android:value RadioGroup android:checkedButton RatingBar android:rating SeekBar android:progress TabHost android:currentTab TextView android:text TimePicker android:hour, android:minute
<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+``}"
/>
複製程式碼
複製程式碼 以上為具體在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<>();
4、MVVM Sample MVVM中跟MVP中一樣,將三層劃分的很清楚,Activity和xml layout充當View,ViewModel處理業務邏輯以及獲取資料,弱化Model。 很多程式碼跟前面類似,這裡只列出核心程式碼,ViewModel層的
public interface BaseViewModel { void onDestroy(); }
public abstract class AbstractViewModel implements BaseViewModel { public T mViewDataBinding; public AbstractViewModel(T viewDataBinding) { this.mViewDataBinding=viewDataBinding; }
@Override
public void onDestroy() {
mViewDataBinding.unbind();
}
複製程式碼
}
public class SampleViewModel extends AbstractViewModel {
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變得更簡潔,修改任何一方,兩者都會保持資料同步。
5、MVVM 總結 看起來MVVM很好的解決了MVC和MVP的不足,但是由於資料和檢視的雙向繫結,導致出現問題時不太好定位來源,有可能資料問題導致,也有可能業務邏輯中對檢視屬性的修改導致。如果專案中打算用MVVM的話可以考慮使用官方的架構元件ViewModel、LiveData、DataBinding去實現MVVM
關於MVC,MVP,MVVM如何選擇的探討 前面在介紹MVC、MVP、MVVM時並沒有去詳細列出他們的優缺點,主要原因有兩個:
網上這方面總結的還是挺多的 其實關於架構,設計,模組化等等,它們的優缺點沒有絕對的,主要看實現者如何去做 比如在mvp中我們要實現根據業務邏輯和頁面邏輯做很多Present和View的具體實現,如果這些case太多,會導致程式碼的可讀性變差。但是通過引入contract契約類,會讓業務邏輯變得清晰許多。因此不管是用哪種設計模式,只要運用得當,都可以達到想要的結果。 如果非要說怎麼選的話,以我淺薄的知識建議如下:
如果專案簡單,沒什麼複雜性,未來改動也不大的話,那就不要用設計模式或者架構方法,只需要將每個模組封裝好,方便呼叫即可,不要為了使用設計模式或架構方法而使用。 對於偏向展示型的app,絕大多數業務邏輯都在後端,app主要功能就是展示資料,互動等,建議使用mvvm。 對於工具類或者需要寫很多業務邏輯app,使用mvp或者mvvm都可。 如果想通過一個專案去學習架構和設計模式,建議用MVC然後在此基礎上慢慢挖掘改進。最後你可能發現,改進的最終結果可能就變成了mvp,mvvm。