論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)是定義了各種控件以及通過(guò)XML文件來(lái)組裝視圖布局界面,iOS的視圖的實(shí)現(xiàn)也是定義了各種控件以及通過(guò)XIB或SB來(lái)組裝視圖布局界面沮协; Android的控制部分則是通過(guò)Activity來(lái)實(shí)現(xiàn)龄捡,而iOS的控制部分則是通過(guò)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)闆](méi)有標(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éi)有人去想著抽象出M層游盲,而是用了如下方法來(lái)解決問(wèn)題:

  • 客戶端和服務(wù)器之間交互的數(shù)據(jù)報(bào)文是否可以定義出一個(gè)個(gè)只有屬性而沒(méi)有方法的數(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ù)而沒(méi)有方法的對(duì)象數(shù)據(jù)模型統(tǒng)一放到一個(gè)地方链峭,然后給他們定義為M模型層(呼!終于給出模型層的定義了又沾,但是:Are you kidding me??)。這樣C層就不會(huì)再出現(xiàn)XML或JSON解析以及直接讀取報(bào)文的代碼了熙卡!而是把這部分代碼挪到模型層了(大家來(lái)看啊杖刷,我終于應(yīng)用上了MVC框架了!)驳癌。 好了滑燃!瘦身第一步成功。但是但是颓鲜,問(wèn)題還在啊表窘,我的業(yè)務(wù)邏輯還是一大片在C層啊,看來(lái)MVC這種框架也不過(guò)如此疤鸨酢乐严!根本沒(méi)有解決我的問(wèn)題。不行衣摩,我不能再用MVC這種框架來(lái)開發(fā)我的應(yīng)用了昂验,我要另找它法,要繼續(xù)對(duì)C層瘦身。

  • 我的某個(gè)界面和某個(gè)業(yè)務(wù)邏輯是綁定在一塊的既琴,這個(gè)界面的展示是通過(guò)調(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è)視圖模型層了涎劈!(不過(guò)哪里好像不對(duì),視圖模型層設(shè)計(jì)到了視圖阅茶、模型蛛枚、視圖模型層三方面的交互和耦合) 不過(guò)沒(méi)有關(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)試。怎么辦镰禾? 沒(méi)有關(guān)系思币,只要是能將C層的代碼瘦身這些又算什么鹿响。。谷饿。大不了就是多趟一點(diǎn)坑惶我,多搞幾次培訓(xùn)就好了。 嗯博投! 就這么辦绸贡,那我把這部分代碼也放入到VM層里面去吧。

    毅哗。听怕。。虑绵。呼D虿t。〕峋Γ?C層終于瘦身成功声搁。然后大家看啊,我的C層里面真的是什么代碼也沒(méi)有了捕发。疏旨。。 它不再處理視圖的事件了扎酷,因?yàn)槭录孯AC給處理了檐涝、它也不處理視圖的刷新和業(yè)務(wù)邏輯的調(diào)用了因?yàn)樽屢晥D模型MV給處理掉了、他也不處理數(shù)據(jù)的解析了因?yàn)樽屇P蛯咏o替換掉了法挨。嗯谁榜。。凡纳。惰爬。我要給這種沒(méi)有C層或者不需要C層的框架起個(gè)名字,叫什么好呢惫企? 就叫:MVVM吧。陵叽。狞尔。 我的應(yīng)用可以不要C層了,然后我就奔走相告巩掺。將C層無(wú)用大白于天下偏序。。

真的是這樣嗎胖替?答案是NOQ腥濉TビА!

首先我想說(shuō)的是一個(gè)優(yōu)秀的框架中各層次的拆分并不是簡(jiǎn)單的將代碼進(jìn)行歸類和劃分端朵,層次的劃分是橫向的好芭,而模塊的劃分則是縱向的 。 這其中涉及到了層次之間的耦合性和職責(zé)的劃分冲呢,以及層與層之間的交互接口定義和方式舍败,同時(shí)層內(nèi)的設(shè)計(jì)也應(yīng)該具有高度的內(nèi)聚性和結(jié)構(gòu)性。而這些設(shè)計(jì)的要求并沒(méi)有在所謂的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è)過(guò)程不需要再單獨(dú)編碼去處理厕诡。這個(gè)技術(shù)就和早年MFC里面的DDX/DDV技術(shù)相似。MVVM只是一種數(shù)據(jù)綁定技術(shù)的變種而不足以稱為框架营勤×橄樱框架中的層的要素要具有職責(zé)和功能的屬性。就MVVM中所定義的M只能理解為純數(shù)據(jù)冀偶⌒训冢縱觀整個(gè)iOS和android中的所有系統(tǒng)框架庫(kù)都沒(méi)有出現(xiàn)過(guò)讓一批數(shù)據(jù)結(jié)構(gòu)組成一個(gè)層的概念。即使如所謂的存儲(chǔ)層也是數(shù)據(jù)庫(kù)和表以及數(shù)據(jù)庫(kù)引擎三者的結(jié)合體為一層进鸠。 其實(shí)之所以說(shuō)控制器膨脹根源在于我們的手寫布局視圖在控制器中完成這里占用了非常多的代碼稠曼, 業(yè)務(wù)處理和實(shí)現(xiàn)也在控制器中完成。蘋果和Google已經(jīng)給出了通過(guò)SB和XML來(lái)實(shí)現(xiàn)視圖的構(gòu)建客年。至于復(fù)雜的業(yè)務(wù)邏輯也完全可以通過(guò)拆分為多個(gè)子視圖控制器或者多個(gè)Fragment 來(lái)完成霞幅。請(qǐng)問(wèn)如果一個(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)烫饼,也就是和視圖以及控制之間沒(méi)有任何的關(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è)干巴巴的只有屬性而沒(méi)有方法的數(shù)據(jù)模型铝量。其實(shí)這里面涉及到一個(gè)最基本的設(shè)計(jì)原則倘屹,那就是面向?qū)ο蟮幕驹O(shè)計(jì)原則:就是什么是類?類應(yīng)該是一個(gè)個(gè)具有相同操作和不同屬性的對(duì)象的抽象慢叨。我想現(xiàn)在任何一個(gè)系統(tǒng)里面都沒(méi)有出現(xiàn)過(guò)一堆只有數(shù)據(jù)而沒(méi)有方法的數(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ù)的緊耦合和相互雙向依賴問(wèn)題巫员,以及和所謂的M層也要緊耦合的存在。所以說(shuō)MVVM里面所謂的VM層的定義也是一個(gè)偽概念甲棍。所謂的VM層這里面只不過(guò)是按頁(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)則將這部分的處理給活生生的代替掉了。也就是通過(guò)RAC所謂的響應(yīng)式和觸發(fā)式這種機(jī)制就能實(shí)現(xiàn)將事件的調(diào)度處理放在任何地方任何時(shí)候都能完成脂矫。這樣做的目的使得我們可以分散和分解代碼。但結(jié)果出現(xiàn)的問(wè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)常拓?,一旦出了問(wèn)題對(duì)于維護(hù)和代碼調(diào)試是不是噩夢(mèng)辉浦?而且使用不當(dāng)就會(huì)出現(xiàn)循環(huán)引用的嚴(yán)重問(wè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)行這種處理能方便的解決問(wè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ī)制灌闺,不僅沒(méi)有簡(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ì)方法最根本的原因就是沒(méi)有對(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)用者知道。通過(guò)接口和實(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層交互的問(wèn)題也需要考慮,因?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ù)代碼的問(wèn)題马胧。

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.reibang.com/u/3c9287519f58

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畜侦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躯保,更是在濱河造成了極大的恐慌夏伊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吻氧,死亡現(xiàn)場(chǎng)離奇詭異溺忧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盯孙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門鲁森,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人振惰,你說(shuō)我怎么就攤上這事歌溉。” “怎么了骑晶?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵痛垛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我桶蛔,道長(zhǎng)匙头,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任仔雷,我火速辦了婚禮蹂析,結(jié)果婚禮上舔示,老公的妹妹穿的比我還像新娘。我一直安慰自己电抚,他們只是感情好惕稻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝙叛,像睡著了一般俺祠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上借帘,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天锻煌,我揣著相機(jī)與錄音,去河邊找鬼姻蚓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匣沼,可吹牛的內(nèi)容都是我干的狰挡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼释涛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼加叁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起唇撬,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤它匕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后窖认,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豫柬,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年扑浸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烧给。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喝噪,死狀恐怖础嫡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酝惧,我是刑警寧澤榴鼎,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站晚唇,受9級(jí)特大地震影響巫财,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哩陕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一翁涤、第九天 我趴在偏房一處隱蔽的房頂上張望桥言。 院中可真熱鬧,春花似錦葵礼、人聲如沸号阿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扔涧。三九已至,卻和暖如春届谈,著一層夾襖步出監(jiān)牢的瞬間枯夜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工艰山, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湖雹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓曙搬,卻偏偏與公主長(zhǎng)得像摔吏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纵装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容