MVC/MVP/MVVM的錯誤認(rèn)識有哪些

本篇文章給大家分享的是有關(guān)MVC/MVP/MVVM的錯誤認(rèn)識有哪些,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

成都創(chuàng)新互聯(lián)公司網(wǎng)絡(luò)公司擁有十載的成都網(wǎng)站開發(fā)建設(shè)經(jīng)驗(yàn),數(shù)千家客戶的共同信賴。提供成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站開發(fā)、網(wǎng)站定制、賣鏈接、建網(wǎng)站、網(wǎng)站搭建、成都響應(yīng)式網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)師打造企業(yè)風(fēng)格,提供周到的售前咨詢和貼心的售后服務(wù)

在Android開發(fā)中使用MVP和MVVM模式早已不是新鮮事了,各種MVP/MVVM相關(guān)的文章、開源庫也已屢見不鮮,甚至是讓人眼花撩亂,那么我為什么還要在這個早已被畫滿涂鴉的黑板上再來涂涂畫畫呢?我還想要警醒讀到這篇文章的各位:你們對于MVX的理解可能并不完全正確!

注:這篇文章里我將使用 MVX 做為MVC、MVP以及MVVM的統(tǒng)稱。

我們都知道MVX的進(jìn)化過程是從滾球獸進(jìn)化到MVC,然后從MVC進(jìn)化到MVP,再從MVP超進(jìn)化到MVVM。那么接下來,按照常規(guī)的套路,我應(yīng)該要介紹什么是MVC,什么是MVP,以及什么是MVVM,并且分別介紹M、V、C/P/VM各自的職責(zé)了。

我的目的是想要糾正一些對MVX的錯誤認(rèn)識,所以前提是你要對MVX有一些了解。為了避免有人在使用MVX時走上彎路,所以決定對我看到的一些關(guān)于MVX的錯誤認(rèn)識進(jìn)行總結(jié)以及糾正。會產(chǎn)生這些錯誤認(rèn)知的原因,經(jīng)我分析,其實(shí)是:沒有真正領(lǐng)會到MVX主義的核心價值觀!其實(shí)MVX的核心思想也很簡單,不要誤會,不是富強(qiáng)、民主、而是將表現(xiàn)層和業(yè)務(wù)層分離 。

表現(xiàn)層和業(yè)務(wù)層分離

表現(xiàn)層和業(yè)務(wù)層分離,Matin Fowler稱之為Separated  Presentation。這里的表現(xiàn)層就是VX,業(yè)務(wù)層就是M。如果有人看到這里發(fā)現(xiàn)了和你認(rèn)為的MVX不一樣的話,那么你對MVX的認(rèn)識很可能就存在錯誤,嚴(yán)重者還可能是走了修正主義路線!

從表現(xiàn)層和業(yè)務(wù)層分離的視角來看,M、V、X不是平等的身份,應(yīng)該是M和V-X。自始自終M的職責(zé)都沒變,變的是V-X,隨著軟件開發(fā)技術(shù)的發(fā)展、交互形式或者交互媒介的不斷改變,表現(xiàn)層的邏輯也越來復(fù)雜,MVX的進(jìn)化過程就是一個不斷探尋處理表現(xiàn)層復(fù)雜邏輯的過程。當(dāng)然從一個形態(tài)進(jìn)化到另一個形態(tài),并不一定是為了解決更復(fù)雜的交互邏輯,也可能是有了一種“更優(yōu)雅”的方式來處理表現(xiàn)層邏輯。

既然已經(jīng)有表現(xiàn)層和業(yè)務(wù)層分離的概念了,那么第一個錯誤觀點(diǎn)就很好解釋了。

錯誤一:Presenter或者ViewModel負(fù)責(zé)處理業(yè)務(wù)邏輯

這是一個很常見的錯誤觀點(diǎn),很多介紹MVP或者M(jìn)VVM的文章都這么說過。正如前面所說,業(yè)務(wù)邏輯是屬于M層的,那Presenter或者ViewModel是干什么的,處理表現(xiàn)層邏輯的嗎?是的,或者說大部分表現(xiàn)層邏輯都是在Presenter或者ViewModel中處理的。之前我將業(yè)務(wù)層之上的這些邏輯稱之為視圖邏輯,現(xiàn)在為了統(tǒng)一就叫做表現(xiàn)層邏輯吧(加個吧字怎么感覺怪怪的)。

我在這里就簡單說一下什么是表現(xiàn)層邏輯,以及View和Presenter/ViewModel又是如何分工的。假設(shè)你的應(yīng)用有一個個人資料的profile頁面,這個頁面有兩種狀態(tài),一種是瀏覽狀態(tài),一種是編輯狀態(tài),通過一個編輯按鈕觸發(fā)狀態(tài)的轉(zhuǎn)換,編輯狀態(tài)時,部分信息項(xiàng)可以進(jìn)行編輯。那這里就有一個明顯的表現(xiàn)層邏輯,那就是點(diǎn)擊按鈕切換瀏覽/編輯狀態(tài)。

現(xiàn)在的MVP的流行形態(tài)(或者變種)叫做Passive  View,它和MVVM一樣現(xiàn)在都傾向于將幾乎所有的表現(xiàn)層邏輯交給Presenter或者ViewModel處理,View層需要做的事情很少,基本上就是接受用戶事件,然后將用戶事件傳遞給Presenter或者ViewModel。以上面的profile頁面的例子來解釋的話就是,View層負(fù)責(zé)接收編輯按鈕的點(diǎn)擊事件,然后通知Presenter/ViewModel,然后Presenter/ViewModel通知View是顯示瀏覽狀態(tài)的視圖還是編輯狀態(tài)的視圖。MVP的示例代碼大概是這樣的:

public class ProfileView {     void initView() {         // 負(fù)責(zé)注冊點(diǎn)擊事件監(jiān)聽器,并將點(diǎn)擊事件通知給presenter         editStateButton.setOnClickListener(new OnClickListener() {             presenter.onEditStateButtonClicked();         })         ...     }  // 顯示瀏覽狀態(tài)視圖,想不到好名字,就叫showNormalState吧     public void showNormalState() {         // 瀏覽狀態(tài)下編輯按鈕提示文字為“編輯”,所有項(xiàng)不可編輯         editStateButton.setText("編輯");         nickName.setEditable(false);         ...     }     public void showEditState() {         // 瀏覽狀態(tài)下編輯按鈕提示文字為“完成”,部分項(xiàng)要設(shè)置為可編輯         editStateButton.setText("完成");         nickName.setEditable(true);         ...     } }   public class ProfilePresenter {     private State curState = State.NORMAL;     public void onEditStateButtonClicked() {         // 按鈕被點(diǎn)擊時,根據(jù)當(dāng)前狀態(tài)判斷View應(yīng)該切換顯示的狀態(tài)         // 這就是表現(xiàn)層邏輯         if (isInEditState()) {             curState = State.NORMAL;             view.showNormalState();         } else {             curState = State.EDIT;             view.showEditState();         }     }     private boolean isInEditState() {         return curState == State.EDIT;     }     @VisibleForTest     void setState(State state) {         curState = state;     } }

注:這個示例代碼只是為了展示表現(xiàn)層邏輯,沒有涉及到Model層,編譯也不會通過的!

能感受到我想表達(dá)的意思嗎?就是Presenter/ViewModel根據(jù)當(dāng)前交互狀態(tài)決定該顯示什么,而View要做的是如何顯示它們。再比如說下拉刷新的場景,由View告訴Presenter/ViewModel,它接收到了下拉事件,然后Presenter/ViewModel再告訴View,讓它去顯示刷新提示視圖,至于這個刷新提示長什么樣就由View來決定。當(dāng)然Presenter/ViewModel也可能會判斷當(dāng)前網(wǎng)絡(luò)不可用,而讓View顯示一個網(wǎng)絡(luò)不可用的提示視圖。

為什么要讓Presenter/ViewModel處理幾乎所有的表現(xiàn)層邏輯呢?主要是為了提高可測試性,將盡可能多的表現(xiàn)層邏輯納入到單元測試的范圍內(nèi)。因?yàn)閷σ晥D控件的顯示等等進(jìn)行單元測試太難了,所以View是基本上沒法進(jìn)行單元測試的,但是Presenter/ViewModel是完全可以進(jìn)行單元測試的:

public class ProfilePresenterTest {     private ProfilePresenter presenter;     private ProfileView view;     @Test     public void testShowEditStateOnButtonClick() {         // 瀏覽狀態(tài)下點(diǎn)擊編輯按鈕,驗(yàn)證View是否顯示了編輯狀態(tài)視圖         // 也就是驗(yàn)證view.showEditState()方法是否被調(diào)用了         presenter.setState(State.NORMAL);         presenter.onEditStateButtonClicked();         Mockito.verify(view).showEditState();     }     @Test     public void testShowNormalStateOnButtonClick() {         // 編輯狀態(tài)下點(diǎn)擊完成按鈕,驗(yàn)證View是否顯示了瀏覽狀態(tài)視圖         // 也就是驗(yàn)證view.showNormalState()方法是否被調(diào)用了         presenter.setState(State.EDIT);         presenter.onEditStateButtonClicked();         Mockito.verify(view).showNormalState();     } }

你看,這些表現(xiàn)層邏輯就都能進(jìn)行單元測試了吧!大概懂我意思了吧?

MVC/MVP/MVVM的錯誤認(rèn)識有哪些

OK,現(xiàn)在你已經(jīng)知道表現(xiàn)層了,那業(yè)務(wù)層又是干什么用的呢?現(xiàn)在我們就要開始談到M了。

M是什么?M是指那些喜歡從受虐中獲得性……哎呀,不好意思,搞混了!哎~學(xué)識淵博就是麻煩!M者,Model也,再長一點(diǎn)就是Domain  Model,中文名字叫領(lǐng)域模型。我們看一下維基百科上對Domain model的定義:

  • In software engineering, a domain model is a conceptual model of the domain  that incorporates both behaviour and data.

怎么樣,是不是很通俗易懂呀?當(dāng)然不是!剛剛開始有點(diǎn)理解Model層是處理業(yè)務(wù)邏輯的,現(xiàn)在又來了個抖MMM……Domain,我都不知道該往哪里去想了!Domain,簡單點(diǎn)就把它理解成業(yè)務(wù),我覺得都沒啥問題。我這里引用這句話,主要是想強(qiáng)調(diào),Model層包含了業(yè)務(wù)數(shù)據(jù)以及對業(yè)務(wù)數(shù)據(jù)的操作(behaviour   and data),也是為了引出第二個錯誤觀點(diǎn)。

錯誤二:Model就是靜態(tài)的業(yè)務(wù)數(shù)據(jù)

我們做業(yè)務(wù)模塊開發(fā)時,會經(jīng)常定義一些數(shù)據(jù)結(jié)構(gòu)類,比如個人資料可能會對應(yīng)一個UserProfile類,一條訂單數(shù)據(jù)可能會對應(yīng)一個Order類,這些類沒有任何邏輯,只有一些簡單的getter、setter方法。有些人會認(rèn)為像UserProfile或者Order這樣的數(shù)據(jù)結(jié)構(gòu)類就是Model。

我們已經(jīng)強(qiáng)調(diào)了,Model層包含了業(yè)務(wù)數(shù)據(jù)以及對業(yè)務(wù)數(shù)據(jù)的操作。像UserProfile或者Order這樣的數(shù)據(jù)結(jié)構(gòu)類的實(shí)例甚至都不能稱之為對象,可以看一下Uncle  Bob的Classes vs. Data  Structures這篇文章,對象是有行為的,一個數(shù)據(jù)結(jié)構(gòu)實(shí)例沒有行為,連對象都稱不上,怎么能代表Model層呢!

靜態(tài)的業(yè)務(wù)數(shù)據(jù)不能代表Model層,業(yè)務(wù)數(shù)據(jù)以及針對業(yè)務(wù)數(shù)據(jù)的操作共同構(gòu)成了Model層,這也就是業(yè)務(wù)邏輯。再舉個例子說一下吧,假設(shè)你在做一個叫“掘鐵”的app,這個app現(xiàn)在只有一個頁面,用來展示推薦的博客列表。OK,我們?nèi)绻肕VP的形式該怎么寫呢?我們就先不管和Model層完全沒有交互的View了,Presenter層除了處理表現(xiàn)層邏輯外,還要向Model層發(fā)出業(yè)務(wù)指令,注意,Presenter并不處理業(yè)務(wù)邏輯,真正的業(yè)務(wù)邏輯還是由Model層完成。示例代碼大概是下面這樣:

public class RecommendBlogFeedPresenter {     private RecommendBlogFeedView view;     private BlogMode model;     public void onStart() {         view.showLoadWait();         model.loadRecommendBlogs(new LoadCallback<>() {             @Override             public void onLoaded(List<Blog> blogs) {                 view.showBlogs(blogs);             }         })     } }   public interface BlogModel {     void loadRecommendBlogs(LoadCallback<List<Blog>> callback); } public class BlogModelImpl implements BlogModel {     private BlogFeedRepository repo;     @Override     public void loadRecommendBlogs(LoadCallback<List<Blog>> callback) {         // BlogFeedRepository.fetch()很可能是耗時操作,所以實(shí)際寫的時候會在非主線程執(zhí)行,這里只是示例         callback.onLoaded(repo.fetch("recommend"));     } } public interface BlogFeedRepository {     List<Blog> fetch(String tag); }

什么?你這個BlogModelImpl里就這一行代碼,你跟我說這是業(yè)務(wù)邏輯?大家冷靜一下,把手里的板磚、砍刀、狼牙棒先放下來。BlogModelImpl類里面的邏輯雖然簡單,但是它的確是業(yè)務(wù)邏輯,也正是因?yàn)闃I(yè)務(wù)邏輯比較簡單,所以BlogModelImpl類才會很簡潔。

再從Presenter的角度看一下,為什么loadRecommendBlogs()屬于業(yè)務(wù)邏輯。博客這個概念毫無疑問屬于業(yè)務(wù)概念,根據(jù)前面的解釋應(yīng)該可以判斷出來“獲取推薦的博客列表”不屬于表現(xiàn)層邏輯,那么這個邏輯的實(shí)現(xiàn)就不是Presenter需要關(guān)心的,那就應(yīng)該是Model層的職責(zé),既然是Model層的那就應(yīng)該是業(yè)務(wù)邏輯了;再者,既然博客是業(yè)務(wù)概念,那么Blog就是業(yè)務(wù)數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),loadRecommendBlogs()涉及到對業(yè)務(wù)數(shù)據(jù)Blog的創(chuàng)建及組裝等操作,所以也應(yīng)該是業(yè)務(wù)邏輯。

看到這里,可能有些人會產(chǎn)生一些誤解:所謂的業(yè)務(wù)邏輯處理就是網(wǎng)絡(luò)請求、數(shù)據(jù)庫查詢等數(shù)據(jù)獲取邏輯,即Model層就是負(fù)責(zé)數(shù)據(jù)獲取的,這也是我要說的第三個錯誤觀點(diǎn)。稍等,我先寫個標(biāo)題?

錯誤三:Model層就是負(fù)責(zé)數(shù)據(jù)獲取的

產(chǎn)生這種錯誤認(rèn)識的,說白了還是沒有搞懂業(yè)務(wù)邏輯。當(dāng)然了業(yè)務(wù)邏輯本身就是很抽象的概念,難理解,也很難區(qū)分,我也不敢往細(xì)了去說,因?yàn)檎f多了怕被你們發(fā)現(xiàn)其實(shí)我也是在裸泳。

業(yè)務(wù)邏輯層并不負(fù)責(zé)數(shù)據(jù)的獲取,數(shù)據(jù)的獲取職責(zé)還要在Model層的更下層,這也是為什么我要把的BlogModel的實(shí)現(xiàn)邏輯寫得如此簡單,因?yàn)閿?shù)據(jù)獲取的職責(zé)全部交給了BlogFeedRepository類,Model層只處理業(yè)務(wù)邏輯。BlogFeedRepository是博客列表的倉儲類,BlogModel通過BlogFeedRepository的fetch()方法獲取標(biāo)簽為recommend的博客列表,也就是推薦的博客列表。BlogModel不關(guān)心BlogFeedRepository是如何獲取對應(yīng)博客數(shù)據(jù)的,它可以是從通過網(wǎng)絡(luò)請求獲取的,也可以是從本地?cái)?shù)據(jù)庫中獲取的,數(shù)據(jù)源有任何改變也不應(yīng)該影響到BlogModel中的業(yè)務(wù)邏輯。

那么既然BlogModel中的業(yè)務(wù)邏輯如此簡單,為什么要強(qiáng)行增加這么一個Model層,而不是讓Presenter直接使用BlogFeedRepository類去獲取數(shù)據(jù)呢?

當(dāng)然是有原因的!假設(shè)我們剛才介紹的“掘鐵”app,在僅有一個博客列表頁面的情況下,依然吸引了很多用戶去使用,產(chǎn)品經(jīng)理此時決定嘗試探索變現(xiàn)手段,首先是在博客推薦列表中添加廣告數(shù)據(jù)。再假設(shè),由于廣告數(shù)據(jù)和博客數(shù)據(jù)分屬不同的后端團(tuán)隊(duì),兩邊數(shù)據(jù)尚未整合打通,暫時由客戶端負(fù)責(zé)把廣告數(shù)據(jù)添加到博客列表中。這個時候,BlogModel終于凸顯了它存在的必要性。表現(xiàn)層不負(fù)責(zé)廣告數(shù)據(jù)的獲取與整合,BlogFeedRepository也不能負(fù)責(zé)廣告數(shù)據(jù)的獲取與整合。廣告數(shù)據(jù)的整合是業(yè)務(wù)邏輯,由BlogModel負(fù)責(zé),廣告數(shù)據(jù)的獲取由專門的數(shù)據(jù)倉儲類負(fù)責(zé)。示例代碼如下:

public class BlogModelImpl implements BlogModel {     private BlogFeedRepository blogRepo;     private AdRepository adRepo;     private BlogAdComposeStrategy composeStrategy;     private AdBlogTransform transform;     @Override     public void loadRecommendBlogs(LoadCallback<List<Blog>> callback) {         List<BlogAd> ads = adRepo.fetch("recommend");         List<Blog> blogs = blogRepo.fetch("recommend");         // 在這里把廣告數(shù)據(jù)整合到博客列表中         blogs = composeStrategy.compose(blogs, ads, transform);         callback.onLoaded(blogs);     } } public interface AdRepository {     List<BlogAd> fetch(String tag); } public interface BlogAdComposeStrategy {     List<Blog> compose(List<Blog> blogs, List<BlogAd> ads, AdBlogTransform transoform); } public interface AdBlogTransform {     Blog transform(BlogAd ad); }

考慮到廣告和博客可能有不同的整合策略,可以按需替換不同的實(shí)現(xiàn),所以把整合策略封裝到了BlogAdComposeStrategy接口中。整合策略也屬于業(yè)務(wù)邏輯,但是因?yàn)檎喜呗缘膶?shí)現(xiàn)細(xì)節(jié)這里不需要關(guān)注,所以我覺得不寫出來也行,反正都是我編的。

這里我想表達(dá)的是,獲取廣告數(shù)據(jù)并將廣告數(shù)據(jù)整合到博客列表中也是業(yè)務(wù)邏輯的一部分,如果省略Model層將會造成得把廣告的整合邏輯放到Presenter或者Repository層,這必然都是不合適的。將業(yè)務(wù)邏輯放到了錯誤的層次里,勢必會造成后續(xù)的維護(hù)性和擴(kuò)展性問題。

錯誤四:Model層依賴Presenter/ViewModel層

還有一些人沒有搞清楚Model層和上層的依賴關(guān)系,依賴關(guān)系寫成了雙向的,這是不對的,業(yè)務(wù)層不應(yīng)該依賴表現(xiàn)層,而是應(yīng)該反過來。

實(shí)際上應(yīng)該是Presenter/ViewModel通過接口的形式依賴Model層,Model層完全不依賴Presenter/ViewModel。就像我前面的示例代碼里一樣,Model層必然不會出現(xiàn)任何presenter這樣的單詞,上層通過觀察者模式來監(jiān)聽Model層的數(shù)據(jù)變化(LoadCallback接口也算是一種),Model層也不用關(guān)心上層是Presenter還是ViewModel。

以上就是MVC/MVP/MVVM的錯誤認(rèn)識有哪些,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)站題目:MVC/MVP/MVVM的錯誤認(rèn)識有哪些
網(wǎng)頁地址:http://muchs.cn/article2/iiojoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、面包屑導(dǎo)航網(wǎng)站設(shè)計(jì)公司、網(wǎng)站策劃品牌網(wǎng)站設(shè)計(jì)、網(wǎng)站營銷

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司