![](/attachment/editor/202201/1641694275s59iz.jpg)
一 背景和問(wèn)題
我個(gè)人平時(shí)會(huì)比較慎用“架構(gòu)”這個(gè)詞
一方面是覺(jué)得業(yè)界有很多架構(gòu)大師和架構(gòu)模式,而我的認(rèn)知和實(shí)踐有限;
另一方面是因?yàn)檫@個(gè)詞看著挺高大上、有點(diǎn)務(wù)虛,如果不結(jié)合實(shí)際場(chǎng)景的具體問(wèn)題來(lái)討論,容易陷入“PHP是最好的語(yǔ)言”這樣的辯論賽中。而不同場(chǎng)景中又有各自的問(wèn)題,程序員們通過(guò)自己的理解和思考、針對(duì)實(shí)際場(chǎng)景對(duì)一些架構(gòu)模式進(jìn)行了擴(kuò)展實(shí)踐,以此來(lái)解決遇到的問(wèn)題,也會(huì)基于同一個(gè)模式延伸出一些派生概念。
兵無(wú)常勢(shì),水無(wú)常形。所以,我個(gè)人的觀點(diǎn)是:以要解決的問(wèn)題為出發(fā)點(diǎn),去討論我們要采用的架構(gòu)模式(技術(shù)方案)。
另外,由于我們是站在很多巨人肩膀上的,討論時(shí)可以站在一些如SOLID等軟件設(shè)計(jì)/開(kāi)發(fā)原則的基礎(chǔ)上。
寫(xiě)這篇文章,我也是從解決一些問(wèn)題的目的出發(fā)的:
最近和團(tuán)隊(duì)同學(xué)討論了相關(guān)話題,雖然大多數(shù)同學(xué)在實(shí)踐上基本一致,但具體到話術(shù)、名詞概念和具體使用的理解和實(shí)踐上有些差異(這是很正常的,因?yàn)闃I(yè)界對(duì)同一個(gè)模式的理解和實(shí)踐也不同)。我結(jié)合一些實(shí)際編碼場(chǎng)景做了一番陳述,為了避免后續(xù)重復(fù)大費(fèi)口舌,所以打算寫(xiě)下來(lái),以后有需要直接發(fā)文章鏈接。
由于我個(gè)人的認(rèn)知和實(shí)踐有限,所以也希望能拋(huan)磚(ying)引(lai)玉(pen),讓我學(xué)到更多。
雖然同一個(gè)架構(gòu)模式在不同業(yè)務(wù)/技術(shù)領(lǐng)域的實(shí)施會(huì)有區(qū)別,但同一個(gè)團(tuán)隊(duì)內(nèi)應(yīng)該保持一致性,因?yàn)檫@樣有助于日常的code review、功能模塊的交接backup等活動(dòng),尤其是有利于使用統(tǒng)一的單測(cè)建設(shè)方案來(lái)保障我們的產(chǎn)品質(zhì)量。
實(shí)際問(wèn)題:我最近在開(kāi)發(fā)商家合并發(fā)貨的功能,但由于之前基礎(chǔ)發(fā)貨功能的界面和邏輯并不是我開(kāi)發(fā)的,所以我在修改原有代碼、支持有非常多細(xì)節(jié)邏輯的合并發(fā)貨能力時(shí),就在擔(dān)心對(duì)原有發(fā)(zhong)貨(yao)能力的影響。而這時(shí)候,如果有單測(cè)的保障,我就可以更放心地進(jìn)行功能升級(jí)改造了 —— 別說(shuō)更復(fù)雜的合并發(fā)貨能力了,而這類訴求在復(fù)雜的交易場(chǎng)景里很普遍。
提煉一下我遇到的具體問(wèn)題:
在由不同開(kāi)發(fā)人員持續(xù)迭代、進(jìn)行功能升級(jí)的軟件開(kāi)發(fā)活動(dòng)中,如何保障具有復(fù)雜邏輯的商家經(jīng)營(yíng)工具的產(chǎn)品質(zhì)量。
軟件開(kāi)發(fā)活動(dòng)是整個(gè)流程的核心環(huán)節(jié):接收產(chǎn)品和視覺(jué)設(shè)計(jì)需求/變更作為輸入,然后輸出客戶可用的終端產(chǎn)品。
而統(tǒng)一的軟件開(kāi)發(fā)架構(gòu)模式,則是我們保障軟件開(kāi)發(fā)質(zhì)量的基礎(chǔ)。(這里就不具體展開(kāi)WHY了)
由于討論的是具體面向客戶使用的業(yè)務(wù)場(chǎng)景,少不了客戶操作交互的視圖層(View),所以我從MVC開(kāi)始談起。
二 從表現(xiàn)層的MVC談起
雖然我平時(shí)比較慎用“架構(gòu)”這個(gè)詞,但我平時(shí)喜歡隨手拍一些建筑物。因?yàn)榻ㄖ,?huì)讓我聯(lián)想到軟件的架構(gòu)也應(yīng)該有美感,畢竟Software Architecture這個(gè)概念也是起源于Architecture。
這時(shí)候,架構(gòu)這個(gè)詞就會(huì)給我一種接地氣的感覺(jué):有多少塊磚,每塊磚做什么用、放到哪里去,這塊磚 和 那塊磚怎么黏在一起或互相支撐。當(dāng)然,由于軟件的可移植性、可復(fù)用性,從某些角度來(lái)講,軟件架構(gòu)相比建筑架構(gòu)有其更復(fù)雜的地方。
MVC誕生至今已經(jīng)超過(guò)40年了(Since 1979),10多年前就得到過(guò)很廣泛的討論和實(shí)踐,穿越時(shí)空到今天肯定有其反脆弱性和內(nèi)在核心價(jià)值。雖然如今乍看起來(lái)好像已經(jīng)過(guò)氣、被討論過(guò)千百遍了,但仍然有很多程序員會(huì)有不同理解和看法,或多或少。這是很正常的,上面也提到了部分原因,這里具體再展開(kāi)下。
1 MVC在經(jīng)典三層架構(gòu)里的位置
MVC是一種通用架構(gòu)模式
早期PC時(shí)代應(yīng)用在桌面客戶端,
后來(lái)在Web時(shí)代變得流行(我以前寫(xiě)PHP也用過(guò)相關(guān)MVC框架),
如今在移動(dòng)互聯(lián)網(wǎng)時(shí)代也得到廣泛應(yīng)用。
上面這三個(gè)場(chǎng)景的應(yīng)用,都是面向客戶的,需要交互表現(xiàn)的。
從MVC命名中的View(視圖)也可以看出,MVC模式應(yīng)用在軟件系統(tǒng)架構(gòu)里的表現(xiàn)層。
在業(yè)界某知名公司的官方文檔里,也明確把MVC放在Web Presentation Patterns下。
我之所以沒(méi)有在上圖中對(duì)M-V-C添加箭頭線條,是因?yàn)樵谶@一點(diǎn)上,不同程序員也有不同理解和實(shí)踐。
這是第一個(gè)需要明確的點(diǎn):MVC架構(gòu)模式在多層系統(tǒng)架構(gòu)里的應(yīng)用范圍。
左側(cè) 業(yè)務(wù)表現(xiàn)層-業(yè)務(wù)服務(wù)層-基礎(chǔ)服務(wù)層 是移動(dòng)端三層架構(gòu)模式,未涉及到 C/S 交互;右側(cè)是Web B/S場(chǎng)景的三層架構(gòu)模式。
因?yàn)橛行⿷?yīng)用會(huì)比較簡(jiǎn)單,根本不需要業(yè)務(wù)服務(wù)或基礎(chǔ)服務(wù)層,純粹靠一個(gè)MVC(或者VC)就能交付出一個(gè)Mobile/Web App;
而且在一些業(yè)務(wù)系統(tǒng)里,Web前端/桌面客戶端/移動(dòng)App 也可能會(huì)被簡(jiǎn)化為 大前端/大終端表現(xiàn)層;
所以可能基于不同信息,不同程序員對(duì)此會(huì)有不同認(rèn)知。
但隨著用戶終端應(yīng)用的重要性和復(fù)雜度的提升,已經(jīng)從簡(jiǎn)單應(yīng)用發(fā)展到復(fù)雜多團(tuán)隊(duì)協(xié)同的平臺(tái)型或航母級(jí)應(yīng)用,僅靠一個(gè)MVC來(lái)完成交付是不合適的。
我們也可以反過(guò)來(lái)想,程序員會(huì)把以下代碼放在客戶端代碼的哪一層:
對(duì)Web引擎的擴(kuò)展邏輯。
通信協(xié)議的結(jié)構(gòu)定義,以及相應(yīng)的socket連接和通信代碼。
一個(gè)業(yè)務(wù)相關(guān)且UI無(wú)關(guān)的平臺(tái)開(kāi)放能力。
Crash捕獲、卡頓監(jiān)控、日志埋點(diǎn)等功能實(shí)現(xiàn),比如Android在做APM相關(guān)事情時(shí)會(huì)采用AOP方式,利用ASM、AspectJ等方案來(lái)做字節(jié)碼插樁。
……
2 業(yè)界基于MVC模式的不同實(shí)踐
前面提到不同程序員對(duì)MVC模式的理解和實(shí)踐存在差異
業(yè)界大廠亦然,以下會(huì)結(jié)合業(yè)界一些知名且有影響力的公司在MVC模式上的實(shí)踐,做進(jìn)一步的展開(kāi)討論。
知名公司A
知名公司A在指導(dǎo)開(kāi)發(fā)者使用MVC時(shí),推薦下圖方式:
可以看出在他們的實(shí)踐上:
Controller可以引用View和Model。
View可以引用Model。
這里的Model傾向于是Passive。
同時(shí),他們建議:
在強(qiáng)類型視圖場(chǎng)景,控制器從模型創(chuàng)建并填充ViewModel實(shí)例,該ViewModel 實(shí)例包含要在該視圖上顯示的數(shù)據(jù)。
當(dāng)控制器由于責(zé)任過(guò)多而變得過(guò)于復(fù)雜時(shí),也就是業(yè)界戲稱的“MVC means Massive View Controller”,需要將業(yè)務(wù)邏輯從控制器移出并推入域模型中。
知名公司B
說(shuō)到Massive View Controller,知名公司B在移動(dòng)互聯(lián)網(wǎng)方興未艾的時(shí)候,推薦下圖所示的MVC實(shí)踐方案:
上圖呈現(xiàn)出:
Controller引用View和Model。
Model通過(guò)一些松耦合方式來(lái)觸達(dá)Controller,如廣播通知、callback等,驅(qū)動(dòng)Controller做出響應(yīng)。
View通過(guò)代理模式等方案弱依賴Controller,由Controller對(duì)各種用戶操作、UI渲染訴求做出響應(yīng)。
而View和Model之間是隔離的,Model變化后對(duì)View的更新操作全部由Controller負(fù)責(zé)。
不過(guò)相應(yīng)的官方文檔已經(jīng)被聲明是過(guò)期文檔了,并備注不一定是目前的最佳實(shí)踐。
是的,隨著移動(dòng)互聯(lián)網(wǎng)蓬勃發(fā)展,十年前的“最佳實(shí)踐”被一路多種挑戰(zhàn) —— 在采用這種方案的開(kāi)發(fā)領(lǐng)域中,如何重構(gòu)Massive View Controller為L(zhǎng)ighter View Controller已經(jīng)成為了一個(gè)專題。
對(duì)比和思考
A和B的異同點(diǎn)
相同點(diǎn):Model包含 所需的數(shù)據(jù)結(jié)構(gòu)封裝,以及相應(yīng)數(shù)據(jù)操作的方法定義。即Data + 本地或遠(yuǎn)端的CURD。
差異點(diǎn):在知名公司A給的圖中,View可以引用Model,而在知名公司B給的圖中則不行。
一些問(wèn)題和思考
View有箭頭指向Model,這里的引用關(guān)系是指什么?是View持有Model.Data數(shù)據(jù)對(duì)象,還是View調(diào)用Model.CURD方法。
Controller的本意是Controing Logic,那除了ViewController外,是否還可以有其它的XxController,比如DataSourceController、NotificationController?
從命名上看,既然ViewController 既有View 又有Controller,那為什么把它放在 C里面,而不放在V里面呢?比如當(dāng)我們?cè)趇OS/Android開(kāi)發(fā)中引入MVVM模式后,ViewController或Activity屬于M-VM-V的哪部分呢,代碼放在哪個(gè)目錄下呢?
我有類名使用ViewModel后綴就代表我使用MVVM模式了嗎?
Martin Fowler
作為
《重構(gòu) : 改善既有代碼的設(shè)計(jì)》、《企業(yè)應(yīng)用架構(gòu)模式》等著作的作者;
敏捷軟件開(kāi)發(fā)宣言創(chuàng)作者之一;
MVVM模式誕生時(shí)參考引用的技術(shù)專家。
Martin Fowler給的MVC模式圖如下:
和上面知名公司A和B的圖,又不一樣了,不過(guò)他這里也是認(rèn)為View可以引用Model的。
MVC和DDD
Martin Fowler和《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》作者Eric Evans也討論過(guò)MVC中Model的設(shè)計(jì)理念:
貧血模型:將Model分為簡(jiǎn)(pin)單(xue)的Model數(shù)據(jù)對(duì)象,和處理操作數(shù)據(jù)對(duì)象的Service/Manager/BizLogic等。
示例:為aPerson修改name,則由 CitizenService.changeNameOfPerson( aPerson ) 這種方式來(lái)實(shí)現(xiàn)。
充血模型:將對(duì)應(yīng)領(lǐng)域的處理邏輯放到領(lǐng)域模型中,使得這個(gè)領(lǐng)域模型更飽(chong)滿(xue)。
示例:aPerson要刷牙,則由 aPerson.brushTeeth() 來(lái)實(shí)現(xiàn)。
補(bǔ)充:充血模型更有面向?qū)ο缶幊痰奈兜溃绕涫谴钆浣灰最I(lǐng)域等業(yè)務(wù)場(chǎng)景,更有體感。不過(guò)稍微細(xì)想一下,可能就會(huì)發(fā)現(xiàn)DDD對(duì)設(shè)計(jì)的要求會(huì)更高,從而對(duì)研發(fā)周期和質(zhì)量保障提出了新的要求,并且可能引起對(duì)現(xiàn)有系統(tǒng)的大規(guī)模重構(gòu)。(盒馬的DDD實(shí)踐)
也就是說(shuō),大到MVC各個(gè)模塊的依賴引用關(guān)系,細(xì)到Model中的代碼設(shè)計(jì)方式,業(yè)界都有不同的理念和實(shí)踐。
Java Web開(kāi)發(fā)領(lǐng)域也對(duì)Model的設(shè)計(jì)產(chǎn)生過(guò)非常激烈的討論。
小結(jié)
先拋開(kāi)具體模塊的代碼設(shè)計(jì)方案,基于以上幾種業(yè)界大廠或?qū)<业拿枋,我小結(jié)了以下這張圖并標(biāo)注了待解問(wèn)題:
問(wèn)題一:如何解決MVC中Controller的膨脹臃腫問(wèn)題?
要回答如何解決,需要先思考為什么膨脹。
問(wèn)題二:View能否引用Model?
要回答能否引用,需要先定義引用關(guān)系是什么。
是持有對(duì)象,還是調(diào)用CURD接口操作對(duì)象。
又或者這兩者沒(méi)有必要區(qū)分,因?yàn)槌钟械膶?duì)象本身就可能帶CURD接口。
參考上面相關(guān)資料,目前業(yè)界有的支持、有的反對(duì)。
問(wèn)題三:存在View -> Model,那么是否可以反過(guò)來(lái)存在 Model -> View?
和問(wèn)題二在描述上相反但又有關(guān)聯(lián),如果對(duì)問(wèn)題再進(jìn)一步提問(wèn)的話:
使用 -> 引用關(guān)系,是為了解決什么問(wèn)題?
使用 -> 引用關(guān)系,會(huì)產(chǎn)生什么問(wèn)題?
如同文章開(kāi)頭所說(shuō),以上問(wèn)題需要結(jié)合具體場(chǎng)景來(lái)展開(kāi)(見(jiàn) 實(shí)際案例結(jié)合),盡量從務(wù)虛到務(wù)實(shí)。
本文由培訓(xùn)無(wú)憂網(wǎng)長(zhǎng)沙牛耳教育課程顧問(wèn)老師整理發(fā)布,希望能夠?qū)ο朐陂L(zhǎng)沙參加影視動(dòng)漫培訓(xùn)的學(xué)生有所幫助。更多課程信息可關(guān)注培訓(xùn)無(wú)憂網(wǎng)電腦IT培訓(xùn)頻道或添加老師微信:15033336050
注:尊重原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處和鏈接 http://m.universityresearchassociates.com/news-id-13948.html 違者必究!部分文章來(lái)源于網(wǎng)絡(luò)由培訓(xùn)無(wú)憂網(wǎng)編輯部人員整理發(fā)布,內(nèi)容真實(shí)性請(qǐng)自行核實(shí)或聯(lián)系我們,了解更多相關(guān)資訊請(qǐng)關(guān)注程序開(kāi)發(fā)頻道查看更多,了解相關(guān)專業(yè)課程信息您可在線咨詢也可免費(fèi)申請(qǐng)?jiān)囌n。關(guān)注官方微信了解更多:150 3333 6050