前幾天看了一些關(guān)于MVP的一些文章撩匕,發(fā)展每個人都有自己的見解蜂嗽,并不能達到高度的統(tǒng)一抛虏,不過筆者還是大受裨益博其。今天筆者也談談自己對MVP的一些理解,供大家學習交流迂猴。
由于筆者是一枚iOS開發(fā)慕淡,所以接下來有些地方會結(jié)合iOS來,以期將筆者想說的都講的清楚明白沸毁。
MVP與其說是設計模式峰髓,還不如說是一種程序架構(gòu)范式,說明了我們應該怎樣更好的組織整個項目的代碼和資源以清。
M:Model層儿普,數(shù)據(jù)服務層崎逃,負責數(shù)據(jù)的增刪改查掷倔。在服務端就包括mysql數(shù)據(jù)庫操作、本地cache等个绍;在客戶端就包括調(diào)用服務器API勒葱、各種形式的數(shù)據(jù)緩存等。有些朋友將Model層理解為數(shù)據(jù)模型層巴柿,數(shù)據(jù)模型歸根結(jié)底是數(shù)據(jù)凛虽,設計數(shù)據(jù)模型是為了在面向?qū)ο蟮某绦蛟O計中更好的表達數(shù)據(jù)。所以數(shù)據(jù)模型貫穿于整個應用程序广恢,不應該將Model層單單理解為是數(shù)據(jù)模型凯旋。但Model層往往負責將接收到的數(shù)據(jù)轉(zhuǎn)化為相應的數(shù)據(jù)模型供上層使用。
V:View層钉迷,視圖界面層至非,負責UI的渲染、子視圖的組織糠聪、UI事件荒椭、用戶交互等。在有些網(wǎng)友看來舰蟆,View層是比較輕的一層趣惠,大多數(shù)時候只需要調(diào)用系統(tǒng)的UI控件,綁定需要的UI事件就完事了身害,很多平臺甚至拖拖控件就行味悄。但是實際上是因為平臺為我們做了大多數(shù)的事,我們不需要去考慮怎么有效的渲染界面塌鸯,也不需要去考慮怎樣去實現(xiàn)各種各樣的交互事件(觸摸)侍瑟,只需要關(guān)注應用本身就可以了。
P:Presenter層界赔,有得朋友將其叫作發(fā)布者丢习,百度翻譯為主持人牵触,筆者覺得后者更貼切些。 Presenter既是中間人咐低,在View和Model之間起到橋梁的作用揽思,又是一個獨立的大模塊,封裝了業(yè)務的復雜度见擦,將UI和業(yè)務邏輯拆分開來钉汗,使UI和業(yè)務都可以獨立的進行變化。從整體的數(shù)據(jù)流向上看鲤屡,Presenter從Model層獲取數(shù)據(jù)损痰,并通過接口發(fā)送給View層展示;View層將用戶交互傳遞給Presenter酒来,由Presenter完成相應的業(yè)務邏輯卢未,這其中可能會有Model層的參與,比如Presenter調(diào)用Model層的接口來保存數(shù)據(jù)堰汉。
在理想狀態(tài)下辽社,Model、View翘鸭、Presenter這三層都應該是面向接口(interface/protocol)編程的滴铅,從而達到低耦合,高復用的效果就乓。同時汉匙,由于業(yè)務不依賴于UI,使單元測試也更容易進行生蚁。很多人并沒有單元測試的重要性噩翠,一個網(wǎng)友描述的挺好的,這里直接將原話貼出來:
上面都說了,完全面向接口編程是最理想的狀態(tài)蠢涝。實際情況則是過份的設計大大增加了開發(fā)過程中的復雜度玄呛,降低了開發(fā)的效率,最終可能影響項目的進度和二。所以在實際的項目開發(fā)中徘铝,對MVP的應用深度應該視情況而定:
- 當某一個View需要在項目中的多處復用而View本身相對來說比較復雜時,P層應該面向接口編程,而View只是持有特定接口類型的引用惕它。這里推薦采用代理模式怕午,即在View層定義一個特定的Protocol,讓P層所有需要與該View層對接的類都去實現(xiàn)這個Protocol以便接收View層發(fā)送過來的事件淹魄,這樣同一個View層可以對接多個不同的P層郁惜,View可以方便的進行復用。如:UITableView等甲锡。
- 當我們需要針對同一個業(yè)務經(jīng)常更換用戶界面時兆蕉,為了讓每一次改動都不會對P層造成大的影響,在設計View層時則需要完全面向接口進行設計缤沦,P層持有IView接口的引用虎韵,這樣不管View層的具體類怎么變動,View層和P層的交互始終依賴的是一直未變動的程序接口IView缸废,P層則不需要做大的改動包蓝。
- 如果你沒有諸如切換數(shù)據(jù)存儲方式(mysql->oracle, file->sqlite)等需求時,往往不需要用面向接口的方式來設計Model層呆奕。但有一點還是應該留意:iOS開發(fā)中养晋,大多數(shù)情況下衬吆,我們在Model層都有兩件事去做梁钾,一是調(diào)用網(wǎng)絡接口訪問服務器,再是本地緩存逊抡。數(shù)據(jù)既可以來自于網(wǎng)絡也可以來自于緩存姆泻,但這些細節(jié)對P層來說應該是透明的,我們不能將獲取數(shù)據(jù)的這兩種方式都暴露給P層冒嫡,而是需要以此為基礎設計統(tǒng)一的接口拇勃。這樣做的好處是,一方面簡化了P層對Model層的調(diào)用孝凌,API操作數(shù)據(jù)具有一致性方咆;另一方面,更改緩存機制蟀架,調(diào)整網(wǎng)絡接口都不會對P層產(chǎn)生影響瓣赂。
筆者認為,實現(xiàn)MVP模式的最低原則是:
- View和Model之間不能直接進行交互片拍,必須通過Presenter來交流數(shù)據(jù)煌集;
- 盡量的將業(yè)務邏輯和UI展示分開;
- 盡量使用面向接口的方式來實現(xiàn)MVP三層捌省,特別是對于View與Presenter之間的交互苫纤。
在iOS平臺上,目前大多數(shù)采用的MVC的實現(xiàn),實際上已經(jīng)符合了MVP的特點了卷拘,只是ViewController做了更多的事喊废。ViewController充當了Presenter的角色,在View和Model之間起到了橋梁的作用栗弟,同時封裝了業(yè)務邏輯操禀。除此之外,ViewController還負責界面的生命周期横腿、視圖組織颓屑、頁面跳轉(zhuǎn)等。ViewController雖然屬于View層耿焊,但很多時候我們還是會用它來封裝業(yè)務邏輯揪惦,由于承擔了過多的職責,在有些復雜的情況下罗侯,ViewController往往變得不堪重負器腋,給維護代碼和新增功能都帶來了不利影響。為了給ViewController瘦身钩杰,我們可以采取以下措施纫塌,供參考:
- 將View的展示邏輯放到相應的View中。典型的就是UITableView的delegate和datasource讲弄,我們可以自定義UITableView的子類措左,將相應的代理方式都移植到子類中去,然后暴露一個接收數(shù)據(jù)的方法供ViewController調(diào)用避除,這樣來給ViewController瘦身怎披。
- 將復雜的View-View交互剝離并封裝起來猴蹂。比如界面上的UIScrollView滾動時疏尿,我們需要不斷的調(diào)整界面頭部的透明度。這些View之間的交互本身并不涉及具體的業(yè)務邏輯阁危,我們就可以將其封裝起來群井,達到使ViewController瘦身的目的状飞。另外,如果View-View的交互具有一般性的話书斜,我們還可以在其他需要的地方方便的復用這種交互行為诬辈,一舉兩得。
- 統(tǒng)一處理界面跳轉(zhuǎn)菩佑。界面跳轉(zhuǎn)放到ViewController中再合適不過了自晰,ViewController天生就具備這樣的能力。我們要做的稍坯,就是盡量將跳轉(zhuǎn)統(tǒng)一封裝到基類中酬荞,而不要同樣的跳轉(zhuǎn)邏輯在每個具體的VC中都去寫一遍搓劫。
- 如果一個ViewController包含多個復雜的業(yè)務邏輯,我們應該為這些定義多個Presenter來分別進行處理混巧。ViewController本身只需要創(chuàng)建View和Presenter枪向,并將具體的View將具體的Presenter對應起來就行了。這樣咧党,ViewController就變得孑然一身了秘蛔。
最后給出一個iOS平臺上實現(xiàn)MVP模式的例子,供大家參考:
由于代碼比較多傍衡,這里只貼出主要的接口代碼深员,項目源碼請查看https://github.com/yuexygoodman/MVPExample
1、登錄界面蛙埂,View層接口
@protocol ILoginView<NSObject>
- (void)setPresenter:(id<ILoginPresenter>)presenter;
- (void)loadWithLastAccount:(NSString *)account pwd:(NSString *)pwd;//顯示歷史賬號
- (void)showLoginError:(NSString *)err;//顯示登陸錯誤
- (void)showLoading;//顯示正在登陸
- (void)hideLoading;//登陸完成后隱藏
- (void)navToMain;//切換界面到主界面
@end
2倦畅、登錄界面 Presenter接口
@protocol ILoginPresenter<NSObject>
- (void)setLoginView:(id<ILoginView>)loginView;
- (void)start;
- (void)onLoginWithAccount:(NSString *)account pwd:(NSString *)pwd;//處理登錄邏輯
@end
3、朋友管理界面 View層接口
@protocol IFriendView<NSObject>
- (void)setPresenter:(id<IFriendPresenter>)presenter;
- (void)loadWithFriends:(NSArray<Friend *> *)friends;//顯示列表
- (void)confirmFriend:(Friend *)fri msg:(NSString *)msg;//向用戶展示再次確認窗口
- (void)unShowingForFriend:(Friend *)fri;//取消被刪除朋友的顯示
- (void)showRemoveError:(NSString *)msg;//顯示錯誤
4绣的、朋友管理界面 Presenter接口
@protocol IFriendPresenter<NSObject>
- (void)setFriendView:(id<IFriendView>)friendView;
- (void)start;//啟動
- (void)onRemoveFriend:(Friend *)fri;//刪除業(yè)務
- (void)onSureRemoveFriend:(Friend *)fri;//直接刪除
@end
再次給出Demo地址:https://github.com/yuexygoodman/MVPExample 歡迎大家訪問
對于MVP模式的理解就到此為止了叠赐,下一篇筆者準備繼續(xù)說一說MVVM,以上內(nèi)容屡江,均屬筆者拙見芭概。有什么不對的地方,煩請指教惩嘉。