被誤解的 MVC 和被神化的 MVVM

唐巧的技術(shù)博客

記錄下自己學(xué)習(xí)的點(diǎn)滴

被誤解的 MVC 和被神化的 MVVM

文章目錄

1. 被誤解的 MVC

1.1. MVC 的歷史

1.2. Controller 的臃腫問題何解晾嘶?

1.3. 如何對 ViewController 瘦身利诺?

1.3.1. 將網(wǎng)絡(luò)請求抽象到單獨(dú)的類中

1.3.2. 將界面的拼裝抽象到專門的類中

1.3.3. 構(gòu)造 ViewModel

1.3.4. 專門構(gòu)造存儲類

1.3.5. 小結(jié)

2. 被神化的 MVVM

2.1. MVVM 的歷史

2.2. MVVM 的神化

2.3. MVVM 的作用和問題

2.4. ReactiveCocoa

2.5. 我們應(yīng)該客觀評價(jià) MVVM 和 ReactiveCocoa

2.6. 總結(jié)

3. 版權(quán)說明

被誤解的 MVC

MVC 的歷史

MVC,全稱是 Model View Controller,是模型 (model)-視圖 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟件開發(fā)框架。

MVC 的概念最早出現(xiàn)在二十世紀(jì)八十年代的 施樂帕克 實(shí)驗(yàn)室中(對,就是那個(gè)發(fā)明圖形用戶界面和鼠標(biāo)的實(shí)驗(yàn)室),當(dāng)時(shí)施樂帕克為 Smalltalk 發(fā)明了這種軟件設(shè)計(jì)模式她混。

現(xiàn)在,MVC 已經(jīng)成為主流的客戶端編程框架泊碑,在 iOS 開發(fā)中坤按,系統(tǒng)為我們實(shí)現(xiàn)好了公共的視圖類:UIView,和控制器類:UIViewController馒过。大多數(shù)時(shí)候臭脓,我們都需要繼承這些類來實(shí)現(xiàn)我們的程序邏輯,因此腹忽,我們幾乎逃避不開 MVC 這種設(shè)計(jì)模式来累。

但是,幾十年過去了窘奏,我們對于 MVC 這種設(shè)計(jì)模式真的用得好嗎嘹锁?其實(shí)不是的,MVC 這種分層方式雖然清楚着裹,但是如果使用不當(dāng)领猾,很可能讓大量代碼都集中在 Controller 之中,讓 MVC 模式變成了 Massive View Controller 模式。

Controller 的臃腫問題何解摔竿?

很多人試圖解決 MVC 這種架構(gòu)下 Controller 比較臃腫的問題面粮。我還記得半年前 InfoQ 搞了一次移動座談會,當(dāng)時(shí) BeeFramework 和 Samurai-Native 的作者 老郭 問了我一句話:「什么樣的內(nèi)容才應(yīng)該放到 Controller 中继低?」熬苍。但是當(dāng)時(shí)因?yàn)闀r(shí)間不夠,我沒能展開我的觀點(diǎn)袁翁,這次正好在這里好好談?wù)勎覍τ谶@個(gè)問題的想法柴底。

我們來看看 MVC 這種架構(gòu)的特點(diǎn)。其實(shí)設(shè)計(jì)模式很多時(shí)候是為了 Don't repeat yourself 原則來做的梦裂,該原則要求能夠復(fù)用的代碼要盡量復(fù)用,來保證重用盖淡。在 MVC 這種設(shè)計(jì)模式中年柠,我們發(fā)現(xiàn) View 和 Model 都是符合這種原則的。

對于 View 來說褪迟,你如果抽象得好冗恨,那么一個(gè) App 的動畫效果可以很方便地移植到別的 App 上,而 Github 上也有很多 UI 控件味赃,這些控件都是在 View 層做了很好的封裝設(shè)計(jì)掀抹,使得它能夠方便地開源給大家復(fù)用。

對于 Model 來說心俗,它其實(shí)是用來存儲業(yè)務(wù)的數(shù)據(jù)的傲武,如果做得好,它也可以方便地復(fù)用城榛。比如我當(dāng)時(shí)在做有道云筆記 iPad 版的時(shí)候揪利,我們就直接和 iOS 版復(fù)用了所有的 Model 層的代碼。在創(chuàng)業(yè)做猿題庫客戶端時(shí)狠持,iOS 和 iPad 版的 Model 層代碼再次被復(fù)用上了疟位。當(dāng)然,因?yàn)楹蜆I(yè)務(wù)本身的數(shù)據(jù)意義相關(guān)喘垂,Model 層的復(fù)用大多數(shù)是在一個(gè)產(chǎn)品內(nèi)部甜刻,不太可能像 View 層那樣開源給社區(qū)。

說完 View 和 Model 了正勒,那我們想想 Controller得院,Controller 有多少可以復(fù)用的?我們寫完了一個(gè) Controller 之后章贞,可以很方便地復(fù)用它嗎尿招?結(jié)論是:非常難復(fù)用。在某些場景下,我們可能可以用 addSubViewController 之類的方式復(fù)用 Controller就谜,但它的復(fù)用場景還是非常非常少的怪蔑。

如果我們能夠意識到 Controller 里面的代碼不便于復(fù)用,我們就能知道什么代碼應(yīng)該寫在 Controller 里面了丧荐,那就是那些不能復(fù)用的代碼缆瓣。在我看來,Controller 里面就只應(yīng)該存放這些不能復(fù)用的代碼虹统,這些代碼包括:

在初始化時(shí)弓坞,構(gòu)造相應(yīng)的 View 和 Model。

監(jiān)聽 Model 層的事件车荔,將 Model 層的數(shù)據(jù)傳遞到 View 層渡冻。

監(jiān)聽 View 層的事件,并且將 View 層的事件轉(zhuǎn)發(fā)到 Model 層忧便。

如果 Controller 只有以上的這些代碼族吻,那么它的邏輯將非常簡單,而且也會非常短珠增。

但是超歌,我們卻很難做到這一點(diǎn),因?yàn)檫€是有很多邏輯我們不知道寫在哪里蒂教,于是就都寫到了 Controller 中了巍举,那我們接下來就看看其它邏輯應(yīng)該寫在哪里。

如何對 ViewController 瘦身凝垛?

objc.io 是一個(gè)非常有名的 iOS 開發(fā)博客懊悯,它上面的第一課 《Lighter View Controllers》 上就講了很多這樣的技巧,我們先總結(jié)一下它里面的觀點(diǎn):

將 UITableView 的 Data Source 分離到另外一個(gè)類中梦皮。

將數(shù)據(jù)獲取和轉(zhuǎn)換的邏輯分別到另外一個(gè)類中定枷。

將拼裝控件的邏輯,分離到另外一個(gè)類中届氢。

你想明白了嗎欠窒?其實(shí) MVC 雖然只有三層,但是它并沒有限制你只能有三層退子。所以岖妄,我們可以將 Controller 里面過于臃腫的邏輯抽取出來,形成新的可復(fù)用模塊或架構(gòu)層次寂祥。

我個(gè)人對于邏輯的抽取荐虐,有以下總結(jié)。

將網(wǎng)絡(luò)請求抽象到單獨(dú)的類中

新手寫代碼丸凭,直接就在 Controller 里面用 AFNetworking 發(fā)一個(gè)請求福扬,請求的完數(shù)據(jù)直接就傳遞給 View腕铸。入門一些的同學(xué),知道把這些請求代碼移到另外一個(gè)靜態(tài)類里面铛碑。但是我覺得還不夠狠裹,所以我建議將每一個(gè)網(wǎng)絡(luò)請求直接封裝成類。

把每一個(gè)網(wǎng)絡(luò)請求封裝成對象其實(shí)是使用了設(shè)計(jì)模式中的 Command 模式汽烦,它有以下好處:

將網(wǎng)絡(luò)請求與具體的第三方庫依賴隔離涛菠,方便以后更換底層的網(wǎng)絡(luò)庫。實(shí)際上我們公司的 iOS 客戶端最初是基于 ASIHttpRequest 的撇吞,我們只花了兩天俗冻,就很輕松地切換到了 AFNetworking。

方便在基類中處理公共邏輯牍颈,例如猿題庫的數(shù)據(jù)版本號信息就統(tǒng)一在基類中處理迄薄。

方便在基類中處理緩存邏輯,以及其它一些公共邏輯煮岁。

方便做對象的持久化讥蔽。

大家如果感興趣,可以看我們公司開源的 iOS 網(wǎng)絡(luò)庫:YTKNetwork人乓。它在這種思考的指導(dǎo)下勤篮,不但將 Controller 中的代碼瘦身都毒,而且進(jìn)一步演化和加強(qiáng)色罚,現(xiàn)在它還支持諸如復(fù)雜網(wǎng)絡(luò)請求管理,斷點(diǎn)續(xù)傳账劲,插件機(jī)制戳护,JSON 合法性檢查等功能。

這部分代碼從 Controller 中剝離出來后瀑焦,不但簡化了 Controller 中的邏輯腌且,也達(dá)到了網(wǎng)絡(luò)層的代碼復(fù)用的效果。

將界面的拼裝抽象到專門的類中

新手寫代碼榛瓮,喜歡在 Controller 中把一個(gè)個(gè) UILabel 铺董,UIButton,UITextField 往 self.view 上用 addSubView 方法放禀晓。我建議大家可以用兩種辦法把這些代碼從 Controller 中剝離精续。

方法一:構(gòu)造專門的 UIView 的子類,來負(fù)責(zé)這些控件的拼裝粹懒。這是最徹底和優(yōu)雅的方式重付,不過稍微麻煩一些的是,你需要把這些控件的事件回調(diào)先接管凫乖,再都一一暴露回 Controller确垫。

方法二:用一個(gè)靜態(tài)的 Util 類弓颈,幫助你做 UIView 的拼裝工作。這種方式稍微做得不太徹底删掀,但是比較簡單翔冀。

對于一些能復(fù)用的 UI 控件,我建議用方法一爬迟。如果項(xiàng)目工程比較復(fù)雜橘蜜,我也建議用方法一。如果項(xiàng)目太緊付呕,另外相關(guān)項(xiàng)目的代碼量也不多计福,可以嘗試方法二。

構(gòu)造 ViewModel

誰說 MVC 就不能用 ViewModel 的徽职?MVVM 的優(yōu)點(diǎn)我們一樣可以借鑒象颖。具體做法就是將 ViewController 給 View 傳遞數(shù)據(jù)這個(gè)過程,抽象成構(gòu)造 ViewModel 的過程姆钉。

這樣抽象之后说订,View 只接受 ViewModel,而 Controller 只需要傳遞 ViewModel 這么一行代碼潮瓶。而另外構(gòu)造 ViewModel 的過程陶冷,我們就可以移動到另外的類中了。

在具體實(shí)踐中毯辅,我建議大家專門創(chuàng)建構(gòu)造 ViewModel 工廠類埂伦,參見 工廠模式。另外思恐,也可以專門將數(shù)據(jù)存取都抽將到一個(gè) Service 層沾谜,由這層來提供 ViewModel 的獲取。

專門構(gòu)造存儲類

剛剛說到 ViewModel 的構(gòu)造可以抽獎到一個(gè) Service 層胀莹。與此相應(yīng)的基跑,數(shù)據(jù)的存儲也應(yīng)該由專門的對象來做。在小猿搜題項(xiàng)目中描焰,我們由一個(gè)叫 UserAgent 的類媳否,專門來處理本地?cái)?shù)據(jù)的存取。

數(shù)據(jù)存取放在專門的類中荆秦,就可以針對存取做額外的事情了篱竭。比如:

對一些熱點(diǎn)數(shù)據(jù)增加緩存

處理數(shù)據(jù)遷移相關(guān)的邏輯

如果要做得更細(xì),可以把存儲引擎再抽象出一層萄凤。這樣你就可以方便地切換存儲的底層室抽,例如從 sqlite 切換到 key-value 的存儲引擎等。

小結(jié)

通過代碼的抽取靡努,我們可以將原本的 MVC 設(shè)計(jì)模式中的 ViewController 進(jìn)一步拆分坪圾,構(gòu)造出 網(wǎng)絡(luò)請求層晓折、ViewModel 層、Service 層兽泄、Storage 層等其它類漓概,來配合 Controller 工作,從而使 Controller 更加簡單病梢,我們的 App 更容易維護(hù)胃珍。

另外,不知道大家注意到?jīng)]蜓陌,其實(shí) Controller 層是非常難于測試的觅彰,如果我們能夠?qū)?Controller 瘦身,就可以更方便地寫 Unit Test 來測試各種與界面的無關(guān)的邏輯钮热。移動端自動化測試框架都不太成熟填抬,但是將 Controller 的代碼抽取出來,是有助于我們做測試工作的隧期。

希望本文能幫助大家掌握正確使用 MVC 的姿勢飒责,在下一節(jié)里,我將分享一下我對 MVVM 的看法仆潮。

被神化的 MVVM

MVVM 的歷史

MVVM 是 Model-View-ViewModel 的簡寫宏蛉。

相對于 MVC 的歷史來說,MVVM 是一個(gè)相當(dāng)新的架構(gòu)性置,MVVM 最早于 2005 年被微軟的 WPF 和 Silverlight 的架構(gòu)師 John Gossman 提出拾并,并且應(yīng)用在微軟的軟件開發(fā)中。當(dāng)時(shí) MVC 已經(jīng)被提出了 20 多年了蚌讼,可見兩者出現(xiàn)的年代差別有多大辟灰。

MVVM 在使用當(dāng)中个榕,通常還會利用雙向綁定技術(shù)篡石,使得 Model 變化時(shí),ViewModel 會自動更新西采,而 ViewModel 變化時(shí)凰萨,View 也會自動變化。所以械馆,MVVM 模式有些時(shí)候又被稱作:model-view-binder 模式胖眷。

具體在 iOS 中,可以使用 KVO 或 Notification 技術(shù)達(dá)到這種效果霹崎。

MVVM 的神化

在使用中珊搀,我發(fā)現(xiàn)大家對于 MVVM 以及 MVVM 衍生出來的框架(比如 ReactiveCocoa)有一種「敬畏」感。這種「敬畏」感某種程度上就像對神一樣尾菇,這主要表現(xiàn)在我沒有聽到大家對于 MVVM 的任何批評境析。

我感覺原因首先是 MVVM 并沒有很大程度上普及囚枪,大家對于新技術(shù)一般都不熟,進(jìn)而不敢妄加評論劳淆。另外链沼,ReactiveCocoa 本身上手的復(fù)雜性,也讓很多人感覺到這種技術(shù)很高深難懂沛鸵,進(jìn)而加重了大家對它的「敬畏」括勺。

MVVM 的作用和問題

MVVM 在實(shí)際使用中,確實(shí)能夠使得 Model 層和 View 層解耦曲掰,但是如果你需要實(shí)現(xiàn) MVVM 中的雙向綁定的話疾捍,那么通常就需要引入更多復(fù)雜的框架來實(shí)現(xiàn)了。

對此栏妖,MVVM 的作者 John Gossman 的 批評 應(yīng)該是最為中肯的拾氓。John Gossman 對 MVVM 的批評主要有兩點(diǎn):

第一點(diǎn):數(shù)據(jù)綁定使得 Bug 很難被調(diào)試。你看到界面異常了底哥,有可能是你 View 的代碼有 Bug咙鞍,也可能是 Model 的代碼有問題。數(shù)據(jù)綁定使得一個(gè)位置的 Bug 被快速傳遞到別的位置趾徽,要定位原始出問題的地方就變得不那么容易了续滋。

第二點(diǎn):對于過大的項(xiàng)目,數(shù)據(jù)綁定需要花費(fèi)更多的內(nèi)存孵奶。

某種意義上來說疲酌,我認(rèn)為就是數(shù)據(jù)綁定使得 MVVM 變得復(fù)雜和難用了。但是了袁,這個(gè)缺點(diǎn)同時(shí)也被很多人認(rèn)為是優(yōu)點(diǎn)朗恳。

ReactiveCocoa

函數(shù)式編程(Functional Programming)和響應(yīng)式編程(React Programming)也是當(dāng)前很火的兩個(gè)概念,它們的結(jié)合可以很方便地實(shí)現(xiàn)數(shù)據(jù)的綁定载绿。于是粥诫,在 iOS 編程中,ReactiveCocoa 橫空出世了崭庸,它的概念都非常 新怀浆,包括:

函數(shù)式編程(Functional Programming),函數(shù)也變成一等公民了怕享,可以擁有和對象同樣的功能执赡,例如當(dāng)成參數(shù)傳遞,當(dāng)作返回值等函筋∩澈希看看 Swift 語言帶來的眾多函數(shù)式編程的特性,就你知道這多 Cool 了跌帐。

響應(yīng)式編程(React Programming)首懈,原來我們基于事件(Event)的處理方式都弱了芳来,現(xiàn)在是基于輸入(在 ReactiveCocoa 里叫 Signal)的處理方式。輸入還可以通過函數(shù)式編程進(jìn)行各種 Combine 或 Filter猜拾,盡顯各種靈活的處理即舌。

無狀態(tài)(Stateless),狀態(tài)是函數(shù)的魔鬼挎袜,無狀態(tài)使得函數(shù)能更好地測試顽聂。

不可修改(Immutable),數(shù)據(jù)都是不可修改的盯仪,使得軟件邏輯簡單紊搪,也可以更好地測試。

哇全景,所有這些都太 Cool 了耀石。當(dāng)我看到的時(shí)候,我都雞凍了爸黄!

我們應(yīng)該客觀評價(jià) MVVM 和 ReactiveCocoa

但是但是滞伟,我突然想到,我好象只需要一個(gè) ViewModel 而已炕贵,我完全可以簡單地做一個(gè) ViewModel 的工廠類或 Service 類就可以了梆奈,為什么要引入這么多框架?現(xiàn)有的 MVC 真的有那么大的問題嗎称开?

直到現(xiàn)在亩钟,ReactiveCocoa 在國內(nèi)外還都是在小眾領(lǐng)域,沒有被大量接受成為主流的編程框架鳖轰。不只是在 iOS 語言清酥,在別的語言中,例如 Java 中的 RxJava 也同樣沒有成為主流蕴侣。

我在這里焰轻,不是想說 ReactiveCocoa 不好,也不是想說 MVVM 不好睛蛛,而是想讓大家都能夠有一個(gè)客觀的認(rèn)識鹦马。ReactiveCocoa 和 MVVM 不應(yīng)該被神化胧谈,它是一種新穎的編程框架忆肾,能夠解決舊有編程框架的一些問題,但是也會帶來一些新問題菱肖,僅此而已客冈。如果不能使好的駕馭 ReactiveCocoa,同樣會造成 Controller 代碼過于復(fù)雜稳强,代碼邏輯不易維護(hù)的問題场仲。

總結(jié)

有一些人總是追趕著技術(shù)和悦,有什么新技術(shù)不管三七二十一立馬就用,結(jié)果被各種坑渠缕。

又有一些人鸽素,總是擔(dān)心新技術(shù)帶來的技術(shù)風(fēng)險(xiǎn),不愿意學(xué)習(xí)亦鳞。結(jié)果現(xiàn)在還有人在用 MRC 手動管理引用計(jì)數(shù)馍忽。

而我想說,我們需要保持的是一個(gè)擁抱變化的心燕差,以及理性分析的態(tài)度遭笋。在新技術(shù)的面前,不盲從徒探,也不守舊瓦呼,一切的決策都應(yīng)該建立在認(rèn)真分析的基礎(chǔ)上,這樣才能應(yīng)對技術(shù)的變化测暗。

版權(quán)說明

本文為 InfoQ 中文站特供稿件央串,首發(fā)地址為:文章鏈接。如需轉(zhuǎn)載碗啄,請與 InfoQ 中文站聯(lián)系蹋辅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挫掏,隨后出現(xiàn)的幾起案子侦另,更是在濱河造成了極大的恐慌,老刑警劉巖尉共,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褒傅,死亡現(xiàn)場離奇詭異,居然都是意外死亡袄友,警方通過查閱死者的電腦和手機(jī)殿托,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剧蚣,“玉大人支竹,你說我怎么就攤上這事○矗” “怎么了礼搁?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長目尖。 經(jīng)常有香客問我馒吴,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任饮戳,我火速辦了婚禮豪治,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扯罐。我一直安慰自己负拟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布歹河。 她就那樣靜靜地躺著齿椅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪启泣。 梳的紋絲不亂的頭發(fā)上涣脚,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音寥茫,去河邊找鬼遣蚀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纱耻,可吹牛的內(nèi)容都是我干的芭梯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼弄喘,長吁一口氣:“原來是場噩夢啊……” “哼玖喘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蘑志,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤累奈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后急但,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澎媒,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年波桩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了戒努。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镐躲,死狀恐怖储玫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萤皂,我是刑警寧澤撒穷,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站敌蚜,受9級特大地震影響桥滨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弛车,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一齐媒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纷跛,春花似錦喻括、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唤崭,卻和暖如春拷恨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谢肾。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工腕侄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芦疏。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓冕杠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酸茴。 傳聞我的和親對象是個(gè)殘疾皇子分预,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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