論MVVM偽框架結(jié)構(gòu)和MVC中M的實(shí)現(xiàn)機(jī)制

目錄

一直都有人撰文吹捧MVVM應(yīng)用開發(fā)框架,文章把MVVM說(shuō)的天花亂墜并且批評(píng)包括iOS和android所用的MVC經(jīng)典框架。這篇文章就是想給那些捧臭腳的人們潑潑冷水,雖然有可能招致罵聲一片,但是目的是給那些剛?cè)腴T的小伙伴一些參考和建議,以免誤入歧途。同時(shí)也給那些深陷其中不能自拔的小伙伴們敲敲警鐘,以免其在錯(cuò)誤的道路上越走越遠(yuǎn)。

------ MVVM并非框架,而只是簡(jiǎn)單的文件夾分類 ------

MVVM被引入的前因后果

大概是在2010年左右移動(dòng)端開發(fā)火了起來(lái),起初是iOS,Android, WinPhone三個(gè)大平臺(tái)競(jìng)爭(zhēng),后來(lái)后者退出了角逐,變成了二分天下。從應(yīng)用體系結(jié)構(gòu)以及為開發(fā)者提供的框架體系來(lái)看,兩個(gè)平臺(tái)都是推出了經(jīng)典MVC三層結(jié)構(gòu)的開發(fā)方式,這三層所代表的意義是模型、視圖、控制。這個(gè)開發(fā)框架的初衷其實(shí)也很簡(jiǎn)單:視圖負(fù)責(zé)展示和渲染,模型負(fù)責(zé)業(yè)務(wù)邏輯的實(shí)現(xiàn),控制負(fù)責(zé)調(diào)度視圖的事件以及業(yè)務(wù)邏輯的調(diào)用以及通知視圖的刷新通知。 三部分松散耦合,各司其職。下面是經(jīng)典的MVC框架結(jié)構(gòu):

MVC框架圖

一個(gè)很可惜的事實(shí)是不管是Android和iOS都只對(duì)C和V兩部分進(jìn)行了標(biāo)準(zhǔn)的定義和實(shí)現(xiàn):Android的視圖部分的實(shí)現(xiàn)是定義了各種控件以及通過XML文件來(lái)組裝視圖布局界面,iOS的視圖的實(shí)現(xiàn)也是定義了各種控件以及通過XIB或SB來(lái)組裝視圖布局界面; Android的控制部分則是通過Activity來(lái)實(shí)現(xiàn),而iOS的控制部分則是通過UIViewController來(lái)實(shí)現(xiàn)的。而模型部分呢?因?yàn)槊總€(gè)應(yīng)用的業(yè)務(wù)邏輯和應(yīng)用場(chǎng)景并不相同,所以兩個(gè)平臺(tái)也無(wú)法也不能夠定義出一個(gè)通用的模型層出來(lái),而是把模型層的定義留給了開發(fā)者來(lái)實(shí)現(xiàn)。然而這為我們的開發(fā)者在使用MVC框架開發(fā)應(yīng)用時(shí)埋下了隱患。

早期的應(yīng)用開發(fā)相對(duì)簡(jiǎn)單,因?yàn)闆]有標(biāo)準(zhǔn)的模型層的定義,而控制層又在工程生成時(shí)留下了很多可供開發(fā)者寫代碼的地方,所以很多開發(fā)人員就自然而然的將業(yè)務(wù)邏輯、網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作、報(bào)文拼裝和解析等等全部代碼都放入了控制層里面去了,根本就不需要什么模型層的定義。 這樣隨著時(shí)間的推移和應(yīng)用的復(fù)雜增加,就出現(xiàn)了C層膨脹的情況了。一個(gè)控制器的代碼可能出現(xiàn)了好幾千行的場(chǎng)景。于是乎有人就開始找解決方案來(lái)為C層瘦身了。又一個(gè)很可惜的事實(shí)是還沒有人去想著抽象出M層,而是用了如下方法來(lái)解決問題:

  • 客戶端和服務(wù)器之間交互的數(shù)據(jù)報(bào)文是否可以定義出一個(gè)個(gè)只有屬性而沒有方法的數(shù)據(jù)對(duì)象呢?這樣在處理和渲染界面時(shí)就不需要和原始的XML或者JSON或者其他的格式報(bào)文交互了,只要操作數(shù)據(jù)對(duì)象就好了。于是解決方案就是根據(jù)客戶端和服務(wù)器之間交互報(bào)文定義出一個(gè)個(gè)的數(shù)據(jù)模型,然后再開發(fā)出一套XML或者JSON和數(shù)據(jù)模型之間互轉(zhuǎn)的解析器來(lái)。最后將這一個(gè)個(gè)只有數(shù)據(jù)而沒有方法的對(duì)象數(shù)據(jù)模型統(tǒng)一放到一個(gè)地方,然后給他們定義為M模型層(呼!終于給出模型層的定義了,但是:Are you kidding me??)。這樣C層就不會(huì)再出現(xiàn)XML或JSON解析以及直接讀取報(bào)文的代碼了!而是把這部分代碼挪到模型層了(大家來(lái)看啊,我終于應(yīng)用上了MVC框架了!)。 好了!瘦身第一步成功。但是但是,問題還在啊,我的業(yè)務(wù)邏輯還是一大片在C層啊,看來(lái)MVC這種框架也不過如此??!根本沒有解決我的問題。不行,我不能再用MVC這種框架來(lái)開發(fā)我的應(yīng)用了,我要另找它法,要繼續(xù)對(duì)C層瘦身。

  • 我的某個(gè)界面和某個(gè)業(yè)務(wù)邏輯是綁定在一塊的,這個(gè)界面的展示是通過調(diào)用某個(gè)業(yè)務(wù)邏輯來(lái)實(shí)現(xiàn)的,業(yè)務(wù)邏輯完成后要直接更新這個(gè)界面。這種緊密的調(diào)用和更新關(guān)系根本就不需要C層的介入。因此可以將這部分界面的更新刷新和業(yè)務(wù)邏輯的調(diào)用綁定在一塊, 二者結(jié)合為一個(gè)封閉而獨(dú)立的整體并形成獨(dú)立的類。這樣把這個(gè)類的代碼抽離出來(lái)了,存放到一個(gè)單獨(dú)的文件夾中。我把這個(gè)部分叫什么好呢?對(duì)了就叫視圖模型層VM吧!視圖模型層中的類定義了一個(gè)給外部使用的唯一接口來(lái)供C層調(diào)用。這樣我終于把一大部分代碼從C層中抽離出來(lái)了。我已經(jīng)成功的實(shí)現(xiàn)了C層的進(jìn)一步瘦身,并抽象出了一個(gè)視圖模型層了!(不過哪里好像不對(duì),視圖模型層設(shè)計(jì)到了視圖、模型、視圖模型層三方面的交互和耦合) 不過沒有關(guān)系,反正我的C層進(jìn)一步瘦身成功了!,我看看還可不可以繼續(xù)瘦身C層?

MVVM各層的依賴關(guān)系
  • 我的很多視圖的事件是在C層中處理的,那我是不是可以把C層的事件處理也拿出來(lái)呢? 干脆就拿出來(lái)吧。但是怎么拿出來(lái)呢?于是乎我又不停的尋找,終于找到一個(gè)叫RAC的東西了,這個(gè)東西好啊,他可以負(fù)責(zé)處理視圖的各種事件,以及可以負(fù)責(zé)連續(xù)的網(wǎng)絡(luò)調(diào)用。等等。。。 RAC就是有點(diǎn)晦澀難懂!難以學(xué)習(xí),代碼難以閱讀和調(diào)試。怎么辦? 沒有關(guān)系,只要是能將C層的代碼瘦身這些又算什么。。。大不了就是多趟一點(diǎn)坑,多搞幾次培訓(xùn)就好了。 嗯! 就這么辦,那我把這部分代碼也放入到VM層里面去吧。

    。。。。呼?。?! C層終于瘦身成功。然后大家看啊,我的C層里面真的是什么代碼也沒有了。。。 它不再處理視圖的事件了,因?yàn)槭录孯AC給處理了、它也不處理視圖的刷新和業(yè)務(wù)邏輯的調(diào)用了因?yàn)樽屢晥D模型MV給處理掉了、他也不處理數(shù)據(jù)的解析了因?yàn)樽屇P蛯咏o替換掉了。嗯。。。。我要給這種沒有C層或者不需要C層的框架起個(gè)名字,叫什么好呢? 就叫:MVVM吧。。。 我的應(yīng)用可以不要C層了,然后我就奔走相告。將C層無(wú)用大白于天下。。

真的是這樣嗎?答案是NO?。?!

首先我想說(shuō)的是一個(gè)優(yōu)秀的框架中各層次的拆分并不是簡(jiǎn)單的將代碼進(jìn)行歸類和劃分,層次的劃分是橫向的,而模塊的劃分則是縱向的 。 這其中涉及到了層次之間的耦合性和職責(zé)的劃分,以及層與層之間的交互接口定義和方式,同時(shí)層內(nèi)的設(shè)計(jì)也應(yīng)該具有高度的內(nèi)聚性和結(jié)構(gòu)性。而這些設(shè)計(jì)的要求并沒有在所謂的MVVM中體現(xiàn)出來(lái)。

MVVM據(jù)說(shuō)是來(lái)源于微軟的數(shù)據(jù)視圖的雙向綁定技術(shù)。也就是有一個(gè)VM的類來(lái)實(shí)現(xiàn)數(shù)據(jù)的變化更新視圖,視圖的變化更新數(shù)據(jù)的處理,整個(gè)過程不需要再單獨(dú)編碼去處理。這個(gè)技術(shù)就和早年MFC里面的DDX/DDV技術(shù)相似。MVVM只是一種數(shù)據(jù)綁定技術(shù)的變種而不足以稱為框架??蚣苤械膶拥囊匾哂新氊?zé)和功能的屬性。就MVVM中所定義的M只能理解為純數(shù)據(jù)。縱觀整個(gè)iOS和android中的所有系統(tǒng)框架庫(kù)都沒有出現(xiàn)過讓一批數(shù)據(jù)結(jié)構(gòu)組成一個(gè)層的概念。即使如所謂的存儲(chǔ)層也是數(shù)據(jù)庫(kù)和表以及數(shù)據(jù)庫(kù)引擎三者的結(jié)合體為一層。 其實(shí)之所以說(shuō)控制器膨脹根源在于我們的手寫布局視圖在控制器中完成這里占用了非常多的代碼, 業(yè)務(wù)處理和實(shí)現(xiàn)也在控制器中完成。蘋果和Google已經(jīng)給出了通過SB和XML來(lái)實(shí)現(xiàn)視圖的構(gòu)建。至于復(fù)雜的業(yè)務(wù)邏輯也完全可以通過拆分為多個(gè)子視圖控制器或者多個(gè)Fragment 來(lái)完成。請(qǐng)問如果一個(gè)設(shè)計(jì)的足夠好的C層,何來(lái)膨脹這么一說(shuō)!

  • 首先要正確的理解MVC中的M是什么?他是數(shù)據(jù)模型嗎?答案是NO。他的正確定義是業(yè)務(wù)模型。也就是你所有業(yè)務(wù)數(shù)據(jù)和業(yè)務(wù)實(shí)現(xiàn)邏輯都應(yīng)該定義在M層里面,而且業(yè)務(wù)邏輯的實(shí)現(xiàn)和定義應(yīng)該和具體的界面無(wú)關(guān),也就是和視圖以及控制之間沒有任何的關(guān)系,它是可以獨(dú)立存在的,您甚至可以將業(yè)務(wù)模型單獨(dú)編譯出一個(gè)靜態(tài)庫(kù)來(lái)提供給第三方或者其他系統(tǒng)使用。在上面經(jīng)典MVC圖中也很清晰的描述了這一點(diǎn):控制負(fù)責(zé)調(diào)用模型,而模型則將處理結(jié)果發(fā)送通知給控制,控制再通知視圖刷新。因此我們不能將M簡(jiǎn)單的理解為一個(gè)個(gè)干巴巴的只有屬性而沒有方法的數(shù)據(jù)模型。其實(shí)這里面涉及到一個(gè)最基本的設(shè)計(jì)原則,那就是面向?qū)ο蟮幕驹O(shè)計(jì)原則:就是什么是類?類應(yīng)該是一個(gè)個(gè)具有相同操作和不同屬性的對(duì)象的抽象。我想現(xiàn)在任何一個(gè)系統(tǒng)里面都沒有出現(xiàn)過一堆只有數(shù)據(jù)而沒有方法的數(shù)據(jù)模型的集合被定義為一個(gè)單獨(dú)而抽象的模型層來(lái)供大家使用吧 我們不能把一個(gè)保存數(shù)據(jù)模型的文件夾來(lái)當(dāng)做一個(gè)層,這并不符合橫向切分的規(guī)則。所以說(shuō)MVVM里面的所謂對(duì)M層的定義就是一個(gè)偽概念。

  • 上面我已經(jīng)說(shuō)明M層是業(yè)務(wù)模型層而非數(shù)據(jù)模型層,業(yè)務(wù)模型層應(yīng)該封裝所有的業(yè)務(wù)邏輯的實(shí)現(xiàn),并且和具體視圖無(wú)關(guān)。我們不能將一個(gè)視圖的展現(xiàn)邏輯綁死在一個(gè)業(yè)務(wù)處理邏輯里面,因?yàn)橛锌赡艽嬖谝粋€(gè)業(yè)務(wù)邏輯有多種不同的展現(xiàn)形式,也可能界面展示會(huì)隨著應(yīng)用升級(jí)而變化,但是業(yè)務(wù)邏輯是相對(duì)穩(wěn)定的。即使是某個(gè)視圖確實(shí)就跟這個(gè)業(yè)務(wù)是緊密耦合的,也不應(yīng)該做強(qiáng)耦合綁定。所以上面所謂的VM這種將視圖的展示和業(yè)務(wù)的處理邏輯綁定在一塊是非常蹩腳的方式,因?yàn)檫@樣的設(shè)計(jì)方式已經(jīng)完全背離了系統(tǒng)里面最基本的展示和實(shí)現(xiàn)應(yīng)該分離處理原則。而且這種設(shè)計(jì)的思維是和分層的理念是背離的。因?yàn)樗霈F(xiàn)了視圖和業(yè)務(wù)的緊耦合和相互雙向依賴問題,以及和所謂的M層也要緊耦合的存在。所以說(shuō)MVVM里面所謂的VM層的定義也是一個(gè)偽概念。所謂的VM層這里面只不過是按頁(yè)面進(jìn)行的功能拆分而已,根本就談不上所謂的層的概念。

  • 再來(lái)說(shuō)說(shuō)事件處理。經(jīng)典的C層設(shè)計(jì)的目的是負(fù)責(zé)事件處理和調(diào)度,不論是按鈕點(diǎn)擊還是UITableview的delegate以及ListView的Adapter都最好放在C層來(lái)處理,這也是符合C層最本質(zhì)的定義:就是C層是一個(gè)負(fù)責(zé)調(diào)度和控制的模塊,它是V層和M層的粘合劑,他的作用就是處理視圖的事件,然后調(diào)用業(yè)務(wù)邏輯,然后接收業(yè)務(wù)邏輯的處理結(jié)果通知,然后再通知視圖去刷新界面,這就是C層存在的意義。而且系統(tǒng)默認(rèn)也是按這個(gè)方式設(shè)計(jì)的。而RAC的出現(xiàn)則將這部分的處理給活生生的代替掉了。也就是通過RAC所謂的響應(yīng)式和觸發(fā)式這種機(jī)制就能實(shí)現(xiàn)將事件的調(diào)度處理放在任何地方任何時(shí)候都能完成。這樣做的目的使得我們可以分散和分解代碼。但結(jié)果出現(xiàn)的問題呢?就是同一個(gè)單元調(diào)度處理邏輯和功能的構(gòu)建完全放在了一個(gè)地方,但不同的單元邏輯的又分散在不同的地方,無(wú)法去分類統(tǒng)一管理和維護(hù)。因此你無(wú)法一下子就知道某個(gè)功能所有調(diào)度到底是如何實(shí)現(xiàn)以及在哪里實(shí)現(xiàn)的。因?yàn)?strong>RAC將功能構(gòu)建和事件處理完全粘合到一個(gè)大的函數(shù)體內(nèi)部,并且是代碼套代碼的模式,這種方式嚴(yán)重的破壞了面向?qū)ο罄锩娴臉?gòu)建和處理分離的設(shè)計(jì)模式理論。更麻煩的是其高昂的學(xué)習(xí)和維護(hù)成本,代碼閱讀理解困難,以及無(wú)處不在的閉包使用。試想一下這個(gè)對(duì)于一個(gè)初學(xué)者來(lái)說(shuō)是不是噩夢(mèng)?,一旦出了問題對(duì)于維護(hù)和代碼調(diào)試是不是噩夢(mèng)?而且使用不當(dāng)就會(huì)出現(xiàn)循環(huán)引用的嚴(yán)重問題。這樣一來(lái)原本C層一個(gè)調(diào)度總管的職責(zé)被RAC來(lái)接管后,這些處理將變得分散和無(wú)序,當(dāng)我們要做一些統(tǒng)一的管理比如HOOK和AOP方面的東西時(shí)就變得無(wú)法下手了。 不可否認(rèn)的是RAC在處理連續(xù)調(diào)用以及順序響應(yīng)方面有一定的優(yōu)勢(shì)。一個(gè)例子是我們可能有連續(xù)的多個(gè)跟服務(wù)器的網(wǎng)絡(luò)請(qǐng)求,這時(shí)候用RAC進(jìn)行這種處理能方便的解決問題。但是我想說(shuō)的是當(dāng)存在這種場(chǎng)景時(shí),我們更加應(yīng)該將這種連續(xù)的網(wǎng)絡(luò)調(diào)用在M層內(nèi)部消化掉,而只給C層提供一個(gè)簡(jiǎn)易而方便的接口,讓C層根本不需要關(guān)心這種調(diào)用的連續(xù)性。因此可以說(shuō)為了把C層的代碼給消化掉而引入RAC的機(jī)制,不僅沒有簡(jiǎn)化掉系統(tǒng)反而降低了系統(tǒng)的可維護(hù)性和可讀性。RAC機(jī)制根本就不適合用在事件處理中。優(yōu)秀的應(yīng)用和框架并不在代碼的多寡,而是整體系統(tǒng)的代碼簡(jiǎn)單易讀,各部分職責(zé)分明,容易維護(hù)的調(diào)試

------ MVVM被引入的根本原因是對(duì)M層的錯(cuò)誤認(rèn)識(shí)所引起的 ------

MVC中M層實(shí)現(xiàn)的準(zhǔn)則

說(shuō)了那么多,可以總結(jié)出所謂的MVVM其實(shí)并不是一種所謂的框架或者模式,他只是一個(gè)偽框架而已,他只是將功能和處理按文件夾的方式進(jìn)行了劃分,最終的的結(jié)果是系統(tǒng)亂成了一鍋粥。毫無(wú)層次可言,所具有的唯一優(yōu)點(diǎn)是把C層的代碼和功能完全弱化了。其實(shí)出現(xiàn)這種設(shè)計(jì)方法最根本的原因就是沒有對(duì)M層進(jìn)行正確的理解定義和拆分。那么我們應(yīng)該如何正確的來(lái)定義和設(shè)計(jì)M層呢?下面是我個(gè)人認(rèn)為的幾個(gè)準(zhǔn)則(也許跟其他人的理念有出入):

  • 定義的M層中的代碼應(yīng)該和V層和C層完全無(wú)關(guān)的,也就是M層的對(duì)象是不需要依賴任何C層和V層的對(duì)象而獨(dú)立存在的。整個(gè)框架的設(shè)計(jì)最優(yōu)結(jié)構(gòu)是V層不依賴C層而獨(dú)立存在,M層不依賴C層和V層獨(dú)立存在,C層負(fù)責(zé)關(guān)聯(lián)二者,V層只負(fù)責(zé)展示,M層持有數(shù)據(jù)和業(yè)務(wù)的具體實(shí)現(xiàn),而C層則處理事件響應(yīng)以及業(yè)務(wù)的調(diào)用以及通知界面更新。三者之間一定要明確的定義為單向依賴,而不應(yīng)該出現(xiàn)雙向依賴。下面是三層的依賴關(guān)系圖:
三層之間的單向依賴關(guān)系

只有當(dāng)你系統(tǒng)設(shè)計(jì)的不同部分都是單向依賴時(shí),才可能方便的進(jìn)行層次拆分以及每個(gè)層的功能獨(dú)立替換。

  • M層要完成對(duì)業(yè)務(wù)邏輯實(shí)現(xiàn)的封裝,一般業(yè)務(wù)邏輯最多的是涉及到客戶端和服務(wù)器之間的業(yè)務(wù)交互。M層里面要完成對(duì)使用的網(wǎng)絡(luò)協(xié)議(HTTP, TCP,其他)、和服務(wù)器之間交互的數(shù)據(jù)格式(XML, JSON,其他)、本地緩存和數(shù)據(jù)庫(kù)存儲(chǔ)(COREDATA, SQLITE,其他)等所有業(yè)務(wù)細(xì)節(jié)的封裝,而且這些東西都不能暴露給C層。所有供C層調(diào)用的都是M層里面一個(gè)個(gè)業(yè)務(wù)類所提供的成員方法來(lái)實(shí)現(xiàn)。也就是說(shuō)C層是不需要知道也不應(yīng)該知道和客戶端和服務(wù)器通信所使用的任何協(xié)議,以及數(shù)據(jù)報(bào)文格式,以及存儲(chǔ)方面的內(nèi)容。這樣的好處是客戶端和服務(wù)器之間的通信協(xié)議,數(shù)據(jù)格式,以及本地存儲(chǔ)的變更都不會(huì)影響任何的應(yīng)用整體框架,因?yàn)樘峁┙oC層的接口不變,只需要升級(jí)和更新M層的代碼就可以了。比如說(shuō)我們想將網(wǎng)絡(luò)請(qǐng)求庫(kù)從ASI換成AFN就只要在M層變化就可以了,整個(gè)C層和V層的代碼不變。下面是M層內(nèi)部層次的定義圖:
M層內(nèi)部的封裝層次
  • 既然我們的應(yīng)用是一個(gè)整體但又分模塊,那么業(yè)務(wù)層內(nèi)部也應(yīng)該按功能模塊進(jìn)行結(jié)構(gòu)劃分,而不應(yīng)該簡(jiǎn)單且平面的按照和服務(wù)器之間通信的接口來(lái)進(jìn)行業(yè)務(wù)層次的平面封裝。我相信有不少人都是對(duì)M層的封裝就是簡(jiǎn)單的按照和服務(wù)器之間的交互接口來(lái)簡(jiǎn)單的封裝。下面的兩種不同的M層實(shí)現(xiàn)的業(yè)務(wù)封裝方式:
兩種不同的M層封裝實(shí)現(xiàn)

我們還可以進(jìn)一步的對(duì)業(yè)務(wù)邏輯抽象出M層的接口和實(shí)現(xiàn)兩部分,這樣的一個(gè)好處是相同的接口可以有不同的實(shí)現(xiàn)方式,以及M層可以隱藏非常多的內(nèi)部數(shù)據(jù)和方法而不暴露給調(diào)用者知道。通過接口和實(shí)現(xiàn)分離我們還可以在不改變?cè)瓉?lái)實(shí)現(xiàn)的基礎(chǔ)上,重新重構(gòu)業(yè)務(wù)部分的實(shí)現(xiàn),同時(shí)這種模式也很容易MOCK一個(gè)測(cè)試實(shí)現(xiàn),這樣在進(jìn)行調(diào)試時(shí)可以很簡(jiǎn)單的在真實(shí)實(shí)現(xiàn)和MOCK實(shí)現(xiàn)之間切換,而不必每次都和服務(wù)器端進(jìn)行交互調(diào)試,從而實(shí)現(xiàn)客戶端和服務(wù)器之間的分別開發(fā)和調(diào)試。下面是一個(gè)升級(jí)版本的M層體系結(jié)構(gòu):

基于接口的M層實(shí)現(xiàn)
  • M層如何和C層交互的問題也需要考慮,因?yàn)镸層是不需要知道C層和V層的存在的,那么M層在業(yè)務(wù)處理完畢后如何去通知C層呢?方法有很多種:
    • 我們可以為M層的通知邏輯定義Delegate協(xié)議,然后讓C層去實(shí)現(xiàn)這些協(xié)議,然后M層提供一個(gè)delegate屬性來(lái)賦值處理業(yè)務(wù)通知的對(duì)象。
    • 我們也可以定義眾多的NSNotification或者事件總線,然后當(dāng)M層的業(yè)務(wù)處理完畢后可以發(fā)送通知,并且在C層實(shí)現(xiàn)通知的處理邏輯。
    • 我們可以用閉包回調(diào)或者接口匿名實(shí)現(xiàn)對(duì)象的形式來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯完成的通知功能。而且可以定義出標(biāo)準(zhǔn):所有M層對(duì)象的方法的最后一個(gè)參數(shù)都是一個(gè)標(biāo)準(zhǔn)的如下格式的block或者接口回調(diào):
typedef void (^UICallback)(id obj, NSError * error);

這種模式其實(shí)在很多系統(tǒng)中有應(yīng)用到。大家可以參數(shù)考蘋果的CoreLocation.framework中的地理位置反解析的類CLGeocoder的定義。還有一點(diǎn)的是在AFN以及ASI中的網(wǎng)絡(luò)請(qǐng)求部分都是把成功和失敗的處理分成了2個(gè)block回調(diào),但是這里建議在給C層的異步通知回調(diào)里面不區(qū)分2個(gè)block來(lái)調(diào)用,而是一個(gè)block用2個(gè)參數(shù)來(lái)解決。因?yàn)橛锌赡芪覀兊奶幚碇胁还艹晒€是失敗都可能有部分代碼是相似的,如果分開則會(huì)出現(xiàn)重復(fù)代碼的問題。

MVC中M層實(shí)現(xiàn)的簡(jiǎn)單舉例

最后我們以一個(gè)簡(jiǎn)單的用戶體系的登錄系統(tǒng)來(lái)實(shí)現(xiàn)一個(gè)M層。

1.定義標(biāo)準(zhǔn)的M層異步回調(diào)接口:

//定義標(biāo)準(zhǔn)的C層回調(diào)block。這里面的obj會(huì)根據(jù)不同對(duì)象的方法的返回而有差異。
typedef void (^UICallback)(id obj, NSError * error);

//這里定義標(biāo)準(zhǔn)的數(shù)據(jù)解析block,這個(gè)block供M層內(nèi)部解析用,不對(duì)外暴露
typedef id (^DataParse)(id retData, NSError * error);

2.定義所有M層業(yè)務(wù)類的基類,這樣在通用基類里面我們可以做很多處理。比如網(wǎng)絡(luò)層的統(tǒng)一調(diào)用,加解密,壓縮解壓縮,我們還可以做AOP和HOOK方面的處理。

     @interface  ModelBase
          
           //定義一個(gè)停止請(qǐng)求的方法
           -(void) stopRequest;
           /**
             *定義一個(gè)網(wǎng)絡(luò)請(qǐng)求的唯一入口方法
             * url 請(qǐng)求的URL
             * inParam: 入?yún)?             * outParse: 返回?cái)?shù)據(jù)解析block,由派生類實(shí)現(xiàn)
             * callback: C層通知block
             */
           -(void) startRequest:(NSString*)url  inParam:(id)inParam outParse:(DataParse)outParse  callback:(UICallback)callback;
     @end

3.定義一個(gè)用戶類:

    @interface  ModelUser:ModelBase
  
        @property(readonly) BOOL isLogin;
        @property(readonly) NSString *name;
       
       //定義登錄方法,注意這個(gè)登錄方法的實(shí)現(xiàn)內(nèi)部可能會(huì)連續(xù)做N個(gè)網(wǎng)絡(luò)請(qǐng)求,但是我們要求都在login方法內(nèi)部處理,而不暴露給C層。
       -(void)login:(NSString*)name  password:(NSString*)password   callback:(UICallback)callback;
        //定義退出登錄方法
       -(void)logout:(UICallback)callback;
    @end

  

4.定義一個(gè)M層總體系統(tǒng)類(可選),這個(gè)類可以是單例對(duì)象:

    @interface ModelSystem:ModelBase
 
     +(ModelSystem*)sharedInstance;

    //聚合用戶對(duì)象,注意這里是readonly的,也就是C層是不能直接修改用戶對(duì)象,這樣保證了安全,也表明了C層對(duì)用戶對(duì)象的使用權(quán)限。
    @property(readonly)  ModelUser *user;  

    //定義其他聚合的模塊

    @end

5.在C層調(diào)用用戶登錄:

  @implementation LoginViewController

    -(IBAction)handleLogin:(UIButton*)sender
   {
        sender.userInteractionEnabled = NO;
        __weak LoginViewController  *weakSelf = self;
       [[ModelSystem sharedInstance].user  login:@"aaa" password:@"bbb"  callback:^(ModelUser *user, NSError *error){

        if (weakSelf == nil)
               return;
       sender.userInteractionEnabled = YES;
       if (error == nil)
       {
              //登錄成功,頁(yè)面跳轉(zhuǎn)
       }
       else
      {
            //顯示error的錯(cuò)誤信息。。
      }}];
         
   }

   @end

可以看出上面的C層的部分非常簡(jiǎn)單明了,代碼也易讀和容易理解。同時(shí)我們還看到了C層跟本不需要知道M層的登錄實(shí)現(xiàn)到底是如何請(qǐng)求網(wǎng)絡(luò)的,以及請(qǐng)求了幾個(gè)網(wǎng)絡(luò)操作,以及用的什么協(xié)議,以及什么數(shù)據(jù)報(bào)文格式,所有的這一切都封裝在了M層內(nèi)部實(shí)現(xiàn)了。C層所要做的就是簡(jiǎn)單的調(diào)用M層所提供的方法,然后在callback中通知界面更新即可。整個(gè)C層的邏輯也就是幾十行就能搞定了。

具體的模型層設(shè)計(jì)方法請(qǐng)參考M層的設(shè)計(jì)


歡迎大家關(guān)注我的github地址,關(guān)注歐陽(yáng)大哥2013,關(guān)注我的簡(jiǎn)書地址:http://www.itdecent.cn/u/3c9287519f58

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容