淺談軟件開發(fā)架構模式
作者 | 思禽
文章來源 | 阿里巴巴淘系技術團隊
背景和問題
我個人平時會比較慎用“架構”這個詞
1.一方面是覺得業(yè)界有很多架構大師和架構模式,而我的認知和實踐有限;
2.另一方面是因為這個詞看著挺高大上、有點務虛,如果不結合實際場景的具體問題來討論,容易陷入“PHP是最好的語言”這樣的辯論賽中。而不同場景中又有各自的問題,程序員們通過自己的理解和思考、針對實際場景對一些架構模式進行了擴展實踐,以此來解決遇到的問題,也會基于同一個模式延伸出一些派生概念。
兵無常勢,水無常形。
所以,我個人的觀點是:以要解決的問題為出發(fā)點,去討論我們要采用的架構模式(技術方案)。
另外,由于我們是站在很多巨人肩膀上的,討論時可以站在一些軟件設計/開發(fā)原則的基礎上。
寫這篇文章,我也是從解決一些問題的目的出發(fā)的:
1.最近和團隊同學討論了相關話題,雖然大多數(shù)同學在實踐上基本一致,但具體到話術、名詞概念和具體使用的理解和實踐上有些差異(這是很正常的,因為業(yè)界對同一個模式的理解和實踐也不同)。我結合一些實際編碼場景做了一番陳述,為了避免后續(xù)重復大費口舌,所以打算寫下來,以后有需要直接發(fā)文章鏈接。
2.由于我個人的認知和實踐有限,所以也希望能拋(huan)磚(ying)引(lai)玉(pen),讓我學到更多。
3.雖然同一個架構模式在不同業(yè)務/技術領域的實施會有區(qū)別,但同一個團隊內(nèi)應該保持一致性,因為這樣有助于日常的code review、功能模塊的交接backup等活動,尤其是有利于使用統(tǒng)一的單測建設方案來保障我們的產(chǎn)品質(zhì)量。
-
實際問題:我最近在開發(fā)商家合并發(fā)貨的功能,但由于之前基礎發(fā)貨功能的界面和邏輯并不是我開發(fā)的,所以我在修改原有代碼、支持有非常多細節(jié)邏輯的合并發(fā)貨能力時,就在擔心對原有發(fā)(zhong)貨(yao)能力的影響。而這時候,如果有單測的保障,我就可以更放心地進行功能升級改造了 —— 別說更復雜的合并發(fā)貨能力了,而這類訴求在復雜的交易場景里很普遍。
提煉一下我遇到的具體問題:
在由不同開發(fā)人員持續(xù)迭代、進行功能升級的軟件開發(fā)活動中,如何保障具有復雜邏輯的商家經(jīng)營工具的產(chǎn)品質(zhì)量。
軟件開發(fā)活動是整個流程的核心環(huán)節(jié):接收產(chǎn)品和視覺設計需求/變更作為輸入,然后輸出客戶可用的終端產(chǎn)品。
而統(tǒng)一的軟件開發(fā)架構模式,則是我們保障軟件開發(fā)質(zhì)量的基礎。(這里就不具體展開WHY了)
由于討論的是具體面向客戶使用的業(yè)務場景,少不了客戶操作交互的視圖層(View),所以我從MVC開始談起。
從表現(xiàn)層的 MVC 談起
雖然我平時比較慎用“架構”這個詞,但我平時喜歡隨手拍一些建筑物。
因為建筑之美,會讓我聯(lián)想到軟件的架構也應該有美感,畢竟Software Architecture這個概念也是起源于Architecture。
當然,由于軟件的可移植性、可復用性,從某些角度來講,軟件架構相比建筑架構有其更復雜的地方。
這時候,架構這個詞就會給我一種接地氣的感覺:有多少塊磚,每塊磚做什么用、放到哪里去,這塊磚 和 那塊磚怎么黏在一起或互相支撐。
MVC誕生至今已經(jīng)超過40年了(Since 1979),10多年前就得到過很廣泛的討論和實踐,穿越時空到今天肯定有其反脆弱性和內(nèi)在核心價值。
雖然如今乍看起來好像已經(jīng)過氣、被討論過千百遍了,但仍然有很多程序員會有不同理解和看法,或多或少。
這是很正常的,上面也提到了部分原因,這里具體再展開下。
MVC 在經(jīng)典三層架構里的位置
MVC是一種通用架構模式,早期PC時代應用在桌面客戶端,后來在Web時代變得流行(我以前寫PHP也用過相關MVC框架),如今在移動互聯(lián)網(wǎng)時代也得到廣泛應用。
上面這三個場景的應用,都是面向客戶的,需要交互表現(xiàn)的。
從MVC命名中的View(視圖)也可以看出,MVC模式應用在軟件系統(tǒng)架構里的表現(xiàn)層。
在微軟官方文檔里,也明確把MVC放在Web Presentation Patterns下。
我之所以沒有在上圖中對M-V-C添加箭頭線條,是因為在這一點上,不同程序員也有不同理解和實踐。(2)
這是第一個需要明確的點:MVC架構模式在多層系統(tǒng)架構里的應用范圍。
左側 業(yè)務表現(xiàn)層-業(yè)務服務層-基礎服務層 是移動端三層架構模式,未涉及到 C/S 交互;右側是Web B/S場景的三層架構模式。
因為有些應用會比較簡單,根本不需要業(yè)務服務或基礎服務層,純粹靠一個MVC(或者VC)就能交付出一個Mobile/Web App;而且在一些業(yè)務系統(tǒng)里,Web前端/桌面客戶端/移動App 也可能會被簡化為 大前端/大終端表現(xiàn)層;所以可能基于不同信息,不同程序員對此會有不同認知。
但隨著用戶終端應用的重要性和復雜度的提升,已經(jīng)從簡單應用發(fā)展到復雜多團隊協(xié)同的平臺型或航母級應用,僅靠一個MVC來完成交付是不合適的。
也可以反過來想,程序員會把以下代碼放在客戶端代碼的哪一層:
1.對Web引擎的擴展邏輯。
2.通信協(xié)議的結構定義,以及相應的socket連接和通信代碼。
3.一個業(yè)務相關且UI無關的平臺開放能力。
4.Crash捕獲、卡頓監(jiān)控、日志埋點等功能實現(xiàn),比如Android在做APM相關事情時會采用AOP方式,利用ASM、AspectJ等方案來做字節(jié)碼插樁。
5.……
業(yè)界基于 MVC 模式的不同實踐
前面提到不同程序員對MVC模式的理解和實踐存在差異,業(yè)界大廠亦然,比如蘋果、微軟,在自身實踐MVC模式或者向開發(fā)者提供相關文檔時,也一樣有不同的說法。
-
微軟
上圖是微軟2014年的文檔描述。
微軟2021年10月份更新的文檔中使用了下圖:
可以看出微軟在MVC的實踐上:
1.Controller可以引用View和Model。
2.View可以引用Model。
3.這里的Model傾向于是Passive。
值得注意的是,微軟在文檔里寫到:
1.強類型視圖通常使用 ViewModel 類型,旨在包含要在該視圖上顯示的數(shù)據(jù)。 控制器從模型創(chuàng)建并填充 ViewModel 實例。
2.控制器不應由于責任過多而變得過于復雜。 要阻止控制器邏輯變得過于復雜,請將業(yè)務邏輯推出控制器并推入域模型。
-
蘋果 & 斯坦福
上圖分別是蘋果和斯坦福10年+前給的MVC模式圖。之所以把它們放在一起,是因為斯坦福這張圖也是他們iOS開發(fā)課程上的課件,基本也是基于Apple的官方文檔來的。
不過這份官方文檔已經(jīng)是Retired Document,并聲明不一定是目前的最佳實踐:
Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid.
是的,上圖應該產(chǎn)出于移動互聯(lián)網(wǎng)方興未艾的時候,隨著移動互聯(lián)網(wǎng)蓬勃發(fā)展,“最佳實踐”被一路多種挑戰(zhàn) —— MVC也被戲稱為Massive View Controller,如何重構Massive View Controller為Lighter View Controller也成為了一個專題。
上面兩張圖呈現(xiàn)出:
1.Controller引用View和Model,做update操作。
2.Model通過KVO和(廣播)通知來觸達Controller,驅(qū)動Controller做出響應。
3.View通過DataSource、Delegate、Target-Action等方式解耦、弱依賴Controller,由Controller對各種用戶操作、UI渲染訴求做出響應。
4.View和Model之間是隔離的。
微軟和蘋果的異同點
1.相同點:Model包含 所需的數(shù)據(jù)結構封裝,以及相應數(shù)據(jù)操作的方法定義。即Data + 本地或遠端的CURD。
2.差異點:微軟給的圖中,View可以引用Model,而蘋果給的圖中不行。
一些問題和思考
1.View有箭頭指向Model,這里的引用關系是指什么?是View持有Model.Data數(shù)據(jù)對象,還是View調(diào)用Model.CURD方法。
2.Controller的本意是Controing Logic,那除了ViewController外,是否還可以有其它的XxController,比如DataSourceController、NotificationController?
3.從命名上看,既然ViewController 既有View 又有Controller,那為什么把它放在 C里面,而不放在V里面呢?比如當我們在iOS/Android開發(fā)中引入MVVM模式后,ViewController或Activity屬于M-VM-V的哪部分呢,代碼放在哪個目錄下呢?
4.我有類名使用ViewModel后綴就代表我使用MVVM模式了嗎?目前看,上述微軟文檔里不這么認為。
-
Martin Fowler
作為:
1.《重構 : 改善既有代碼的設計》、《企業(yè)應用架構模式》等著作的作者,
2.敏捷軟件開發(fā)宣言創(chuàng)作者之一,
3.MVVM模式誕生地 —— 微軟 —— 參考引用的技術專家,
Martin Fowler畫的MVC模式圖如下:
和上面微軟、蘋果給的圖,又不一樣了 *_*
-
Google
谷歌相關的描述則是:
-
MVC 和 DDD
Martin Fowler和《領域驅(qū)動設計》作者Eric Evans也討論過MVC中Model的設計理念:
1.貧血模型:將Model分為簡(pin)單(xue)的Model數(shù)據(jù)對象,和處理操作數(shù)據(jù)對象的Service/Manager/BizLogic等。
示例:為aPerson修改name,則由 CitizenService.changeNameOfPerson( aPerson ) 這種方式來實現(xiàn)。
2.充血模型:將對應領域的處理邏輯放到領域模型中,使得這個領域模型更飽(chong)滿(xue)。
示例:aPerson要刷牙,則由 aPerson.brushTeeth() 來實現(xiàn)。
補充:充血模型更有面向?qū)ο缶幊痰奈兜?,尤其是搭配交易領域等業(yè)務場景,更有體感。不過稍微細想一下,可能就會發(fā)現(xiàn)DDD對設計的要求會更高,從而對研發(fā)周期和質(zhì)量保障提出了新的要求,并且可能引起對現(xiàn)有系統(tǒng)的大規(guī)模重構。(盒馬的DDD實踐)
也就是說,大到MVC各個模塊的依賴引用關系,細到Model中的代碼設計方式,業(yè)界都有不同的理念和實踐。
Java Web開發(fā)領域也對Model的設計產(chǎn)生過非常激烈的討論。
-
小結
先拋開具體模塊的代碼設計方案,基于以上幾種業(yè)界大廠或大佬的描述,我小結了以下這張圖并標注了待解問題
問題一:如何解決MVC中Controller的膨脹臃腫問題?
要回答如何解決,需要先思考為什么膨脹。
問題二:View能否引用Model?
1.要回答能否引用,需要先定義引用關系是什么。
是持有對象,還是調(diào)用CURD接口操作對象。
又或者這兩者沒有必要區(qū)分,因為持有的對象本身就可能帶CURD接口。
2.參考上面相關資料,目前(1)微軟2021年文檔支持可以引用;(2)蘋果/斯坦福2011年文檔反對。
3.在DinamicX研發(fā)模式中,模板View持有data數(shù)據(jù)對象在內(nèi)部進行填充和渲染。
問題三:存在View -> Model,那么是否可以反過來存在 Model -> View?
和問題二在描述上相反但又有關聯(lián),如果對問題再進一步提問的話:
1.使用 -> 引用關系,是為了解決什么問題?
2.使用 -> 引用關系,會產(chǎn)生什么問題?
如同文章開頭所說,以上問題需要結合具體場景來展開(見 實際案例結合),盡量從務虛到務實。
Redux-like Architecture and Framework
隨著前后端分離得更徹底,終端設備性能和用戶體驗重要性的提升,前端領域也得到了蓬勃發(fā)展,開發(fā)方式也有了比較大的變化,MVC-like方式不再是主流:
1.UI開發(fā)方面從早期的命令式到現(xiàn)在的聲明式。
2.整體應用和業(yè)務邏輯實現(xiàn)方面,從早期的OOP寫法,轉向基于FP的響應式編程。比如Redux的數(shù)據(jù)流、React的Hook特性等。
3.各種框架蓬勃發(fā)展,一些概念和模式的提出、實踐應用方面,我個人認為是領先并影響客戶端的。
其中Redux是一個經(jīng)典案例,并且我覺得Redux官方也挺開放包容的,比如Dan Abramov寫的《You Might Not Need Redux》。
圖源自Redux官網(wǎng),在官網(wǎng)上是一張很形象生動的動圖。
和MVC延伸派生出的MVC Family一樣,Redux提出或重新帶火了數(shù)據(jù)流、狀態(tài)管理等概念,開始影響其它平臺領域,并誕生了一些框架。
比如ReSwift、swift-composable-architecture,以及SwiftUI里的State and Data Flow。
雖然我也寫過點React,但并沒有怎么實踐過Redux。
不過這些變化和影響,是我們在解決問題的過程中需要結合考慮的。
實際案例結合
Redux-like Architecture and Framework
程序 = 數(shù)據(jù)結構 + 算法。—— Nicklaus Wirth,Pascal之父,圖靈獎獲得者
這句話乍看起來可能會有點面向過程設計的感覺,但OOP中的對象其實也是由數(shù)據(jù)+方法組成,而FP則更不用說了。
在編碼開發(fā)活動中,會存在以上3種數(shù)據(jù)結構定義和使用方式:
1.原生數(shù)據(jù)結構,比如list/array、map/dictionary、tuple等。
2.類似MyContact的數(shù)據(jù)結構定義,由服務端返回的數(shù)據(jù)進行轉化,并可能根據(jù)業(yè)務邏輯按需加上一些標志位給Controller消費。
3.類似ContactViewModel這樣的純粹為視圖View服務的數(shù)據(jù)結構定義。
補充:
(1)MyContact 和 ContactViewModel 只是特意區(qū)分的命名,實際上 MyContact 也可以是純粹為視圖View服務的數(shù)據(jù)結構定義。
(2)但是,合適的命名有助于幫助我們思考和編碼,從表達上呈現(xiàn)出我們的傾向和重點。
"There are only two hard things in Computer Science: cache invalidation and naming things."
—— Phil Karlton
常見的多復雜卡片的列表場景
這個場景可以部分回答問題一:為什么Controller會膨脹,以及如何解決。
其它部分答案則落在復雜頁面場景的多delegate、target-action、notification-observer等視圖交互響應的處理邏輯上。
我認為,蘋果/斯坦福之前反對View引用Model,就是導致MVC變成Massive View-Controller的一個原因。
另一個原因我認為是工具鏈只提供了ViewController這樣的Controller模板,隱式教導開發(fā)者都在這里寫代碼。
這也可能是因為十幾年前移動互聯(lián)網(wǎng)還沒發(fā)展起來,移動App的復雜度低,所以蘋果提供了在當時簡單夠用的方案。
當只能由Controller 持有-> Model的時候,那么在多復雜卡片的列表場景中,必須由Controller來更新每個View的屬性/狀態(tài)。
1.MyViewController需要為ContactCell更新它的各種相關屬性,類似的還有AddressCell、PackageCell等。
2.MyViewController在更新AddressCell展示前,可能還需要先為它計算出合適的富文本展示內(nèi)容。
3.MyViewController需要響應不同Cell的點擊交互行為,包含但不限于按鈕點擊、輸入框變化、富文本跳轉、鍵盤起落等。
4.MyViewController需要響應CollectionView/TableView的DataSource/Delegate各種方法實現(xiàn)。
5.MyViewController需要響應Model層的變更通知,或者是另外一個ViewController拋過來的廣播通知。
6.……
然后MyViewController就爆炸了。
針對這種場景,我的解法是:
1.通過讓View->Model,基于工廠模式,把組件化Cell基于數(shù)據(jù)更新的布局邏輯交給View負責,如contactCell.configUIWithModel( contactModel )。這樣有點類似上面DDD提到的充血Model,具備高內(nèi)聚的特點,帶來好處:
a.和微軟說的減輕控制器負擔、推入域模型類似,通過把數(shù)據(jù)驅(qū)動布局的代碼推入組件域內(nèi),減輕了MyViewController的負擔。
b.利于做這部分組件化Cell的UI測試。
c.利于這些組件化視圖復用到其它場景,比如交易管理場景的訂單卡片可以復用到搜索場景中,不用在SeachViewController里復制粘貼一大堆代碼,只需要從Model取一個數(shù)據(jù)對象丟給組件化Cell即可。
工廠模式下,產(chǎn)品的刷漆、烘干、印花等操作會在內(nèi)部完成,不會丟一個模型讓客戶去自己貼logo。MVC的每一部分都可以用不同的設計模式來組合實施,實現(xiàn)解耦或動態(tài)靈活的目標。
2.基于ViewController,拆分出不同職責的擴展,比如MyViewController+Delegate專門復雜響應代理事件處理。
3.定義出其它類型的Controller,比如MyDataSourceController,專門為TableView提供數(shù)據(jù)源,可以類比參考Android中ListView的Adapter+ViewHolder。
對應下圖:
到這里已經(jīng)回答了前面的問題一和問題二。
更多解法可以參考上面提到的微軟給的建議,以及l(fā)ighter view controllers。
這里采用了 VIew -> Model 的方案,用來參與解決Massive View-Controller的問題,并且讓View更容易復用和做UI測試,帶來了好處。
可以結合微軟提的
“控制器不應由于責任過多而變得過于復雜。 要阻止控制器邏輯變得過于復雜,請將業(yè)務邏輯推出控制器并推入域模型”
再進一步討論下。
我的理解和舉例:
1.存在一個輸入框,讓用戶提交物流單號。
2.用戶在輸入過程或者完成輸入后,由View通過delegate模式路由給Controller做校驗,而Controller可能還要進一步依賴Model去做更完整的校驗(如網(wǎng)絡請求到服務端,因為物流單號的規(guī)則很多而且可能動態(tài)更新)。
3.當Controller責任過多、代碼膨脹、過于復雜時,就將物流單號這塊業(yè)務邏輯推入 物流(單號)域模型中,即由View直接通過delegate模式交給 LogisticsModel來做校驗。
4.也是 View -> Model 。
不一定對,拋(huan)磚(ying)引(lai)玉(pen)。
那么,存在VIew -> Model,有什么壞處嗎?
一個 Kotlin 跨平臺場景案例
這里不具體展開講Kotlin及其跨平臺相關內(nèi)容,只是描述從MVC模式做跨平臺遷移時遇到的問題。
這里的ViewController/Activity放在哪里,也和上面的一個問題相呼應。
雖然D-KMP主張通過全新構建工程寫代碼的方式來實踐,但從實際情況出發(fā),絕大多數(shù)現(xiàn)有系統(tǒng)都會是以單點嘗試、漸進式的方式來落地,或發(fā)展、或回撤。
那么,如上圖所示:
1.如果在既有MVC代碼結構中,View -> Model,在這種漸進式遷移場景下,就需要修改View來適配新的ViewModel。
2.如果 View 不引用 Model,則是由 Controller來做膠水層設置更新視圖(命令式UI)。
3.后者的好處是,保持View的獨立性,只需要修改這個業(yè)務場景對應的單個Controller即可。因為View具有可復用性,可能在另外還沒準備遷移/改變的模塊里也有使用。
所以,此處不建議 View -> Model。
那么,誰對呢?
誰對誰錯的務虛討論
當有程序員要推薦使用其它架構模式的時候,通常開頭的一句話就是先說MVC模式的問題。
比如ReSwift在寫 Why ReSwift? 時,開頭第一句話就是:Model-View-Controller (MVC) is not a holistic application architecture.
我個人認為:
1.架構是一個名詞(n.)+動詞(v.)。
架構(n. & v.)是為了幫助 開發(fā)者在交流時有一致的理解、在業(yè)務需要時能夠便于擴展、在出問題時能夠快速定位等等(對抗熵增)。具體還是看采用這個架構的得失,要解決什么問題或帶來什么好處,然后帶來什么成本或付出什么代價。
架構(v.)通過分配每部分代碼的職責并為他們?nèi)∶ê帽萯OS/Android開發(fā)工程師這樣的崗位名稱,讓別人一看就知道是做什么的),然后幾個名字加在一起 形成了架構模式這個抽象概念。
從具體到抽象,然后再由這個抽象概念去指導程序員實踐寫代碼,促進了架構的傳播,比如MVC、MVP、MVVM、MVVM-C、MVI、MV-Whatever,VIPER,Redux and More……
2.有時候并不一定是架構模式的錯,還有可能是平臺/框架在讓架構模式自傳播時采用的具體方案出了點問題,又或者是開發(fā)者自己寫代碼的問題。
3.有時候架構模式之間也不是互斥的,也可以在不同場景下互補。比如在MVC里,每個業(yè)務表現(xiàn)模塊不一定要有三個元素齊聚,甚至也可以 VC-M-VC 共用一個M(可參考斯坦福iOS開發(fā)課程內(nèi)容)。
有一個實際的業(yè)務場景是這么實踐的:
MVC和MVVM在一個業(yè)務場景里相結合。
這里提到了MVVM,前面的Kotlin跨平臺圖,除了涉及到 View -> Model 的引用關系是否應該存在的討論,也涉及到了Declarative-UI和MVVM架構模式等概念,再順便展開下。
-
MVVM 和 MVW
MVC、MVP 和 Declarative-UI 這三個名詞概念都有幾十年歷史了,其中聲明式UI在近十年又開始火了起來;而MVVM則有十幾年歷史。
微軟在2005年提出了MVVM概念,那么微軟會不會認為 MVVM 存在誰對誰錯的問題呢?
在我閱讀完幾篇很贊的微軟官網(wǎng)的文章后,我的感覺是微軟很謙虛和海納百川,并不會很教條式。
比如:
1.在微軟MVC官方文檔中,建議強類型視圖使用 ViewModel 類型,旨在包含要在該視圖上顯示的數(shù)據(jù)。 控制器從模型創(chuàng)建并填充 ViewModel 實例。也就是MVC里也可以用ViewModel。
2.微軟認為自己在2005年提出的MVVM架構模式,有些內(nèi)容和Martin Fowler在2004年提出的PM(表現(xiàn)層模型)概念一致。
3.微軟認為在架構模式上存在著不完全實現(xiàn)或組合實現(xiàn)的情況,也不會跑去說谷歌Android的ViewModel不符合他提出來的規(guī)范。
給我的感覺就好像Angular一樣:
I hereby declare AngularJS to be MVW framework - Model-View Whatever. Where Whatever stands for "whatever works for you".
當然,微軟也給出了他關于MVVM的一些描述:
1.V和VM是一對多的關系,即View 1:n ViewModel。相比于 MVC里 Controller 1:n View,顯然MVC的C更容易膨脹,所以上面也提到了使用多Controller方案。
2.V引用VM,VM操作M,而VM不需要引用V(和MVP不同)。VM作為V的上下文,包含V所需用來展示的數(shù)據(jù),響應V被用戶觸發(fā)的事件。
3.V對M無感知,反過來 M 對 V和VM 也無感知。整體是松散解耦的模式。
4.當VM的屬性值發(fā)生變化時,通過數(shù)據(jù)綁定方式傳達給V。這就需要有機制支撐,有對應的機制MVC也能做數(shù)據(jù)綁定。
基于上述描述我畫了下圖:
總體來說,MVVM是基于事件驅(qū)動的、以數(shù)據(jù)綁定機制為支撐的松散解耦架構模式。
1.它的前提是有系統(tǒng)/平臺/框架的機制支撐,不然實現(xiàn)成本和復雜度有點大。
2.相比MVC,它的優(yōu)勢是視圖控制邏輯不太會膨脹,代碼單元更容易被測試,在可讀性、可維護性上更好。
3.它可能帶來的成本/問題是入門上手較難,簡單場景下使用容易過度設計,復雜場景下出問題調(diào)試比較麻煩。
考慮到ViewController在該場景的薄膠水特質(zhì),以及也參與視圖展示和用戶響應,所以我把它放在V里。
誰對誰錯的務實案例:VIPER 和分層演變
關于誰對誰錯/誰好誰壞,可以再來看一個案例。
-
VIPER架構模式的應用
圖來源于Bohdan Orlov的《iOS Architecture Patterns》
從圖中可以看到ViewController在VIPER中也被放到View里了,而且連接詞用的是 and / or 。
我之前寫過MVC、MVP、MVVM、VIPER等架構模式下的代碼,完全實現(xiàn)或不完全實現(xiàn)。
有一次,我在某個業(yè)務表現(xiàn)模塊里應用VIPER,然后定義了一個XxxNetworkInteractor類,用來負責做網(wǎng)絡請求。
-
VIPER架構模式的演變
后來隨著業(yè)務場景的變化,一個業(yè)務模塊可能需要對接多套網(wǎng)關服務,XxxNetworkInteractor就要做抽象隔離,消除業(yè)務邏輯對多套網(wǎng)關請求的感知,并且應用到其它業(yè)務模塊。
這就有點像 VPER - I - VPER 模式 —— 多個業(yè)務模塊對 Interactor 進行了復用。
-
進化
1.隨著對接網(wǎng)關數(shù)的增加、網(wǎng)關能力的增強,XxxNetworkInteractor也配套建設了更多能力。
2.隨著使用XxxNetworkInteractor的模塊越來越多,不同業(yè)務的一些通用訴求和處理邏輯也隨之增加。
3.隨著XxxNetworkInteractor的功能越來越強大,代碼越來越多,逐漸需要抽離出一個單獨模塊,不管是從提高編譯速度,還是從封裝暴露等角度。
XxxNetworkSDK.framework誕生了。
-
退化
再后來,隨著技術和業(yè)務的變化,底層網(wǎng)關服務ATop成為了領域范圍內(nèi)的事實標準,之前遇到的問題也消失了,而XxxNetworkSDK又帶來一些成本,比如要配套ATop的能力升級進行迭代維護。
此時,XxxNetworkSDK可以退出歷史舞臺了,讓業(yè)務模塊直接面向ATop編程,這樣既降低了維護成本,又帶來了一定的包大小收益。
以后,假如有另外一個領域范圍的業(yè)務場景(采用了XTop),要復用最上層的業(yè)務模塊,由于在另外一個領域范圍,業(yè)務模塊要從 調(diào)用ATop 改為調(diào)用XTop。
那么,接手或負責遷移復用的開發(fā)者,會不會在想:為什么這些業(yè)務模塊直接調(diào)用ATop,而沒有使用一個XxxNetworkInteractor來做抽象隔離呢?
1.如果有,他只要做一件事情:讓XxxNetworkInteractor對接XTop即可。
2.如果沒有,他需要去替換修改每個業(yè)務模塊的調(diào)用代碼、返回值處理等等。
寫在最后,回到問題
不管是務虛的討論,還是務實的案例,或者是微軟和Angular的“whatever works for you”的觀點,結論是沒有誰對誰錯、誰好誰壞,只有實際場景下要解決的核心問題。
兵無常勢,水無常形。
那么,回到想要解決的問題:
在由不同開發(fā)人員持續(xù)迭代、進行功能升級的軟件開發(fā)活動中,如何保障具有復雜邏輯的商家經(jīng)營工具的產(chǎn)品質(zhì)量。
我的想法是以可測性作為手段,來保障功能升級改造或代碼重構后,可在合理時間范圍內(nèi)得到充分回歸驗收,保障相關組件、模塊或整體產(chǎn)品的質(zhì)量。
具體的方案實施上,因為是以可測性為重要關注點,再結合目前的技術方向,我會傾向于采用MVVM。
1.MVVM的架構模式在理解和認知上比較成熟(應該是移動端開發(fā)領域TOP2流行的),便于實施和傳播,并通過模式的名稱來體現(xiàn)要強調(diào)的關注點:可測性。
2.MVVM可以更好地結合SwiftUI+Combine、Kotlin跨平臺等技術方向。
3.MVVM完全版的上手門檻和簡單場景的過度設計問題,可以通過采用不完全版MVVM來解決。
不完全版的MVVM可以cover適用于簡單場景的MVC,近似于 超集-子集 關系。
雖然也可以通過多Controller的方式來解決MVC膨脹問題,但MVC的命名在實踐中就容易讓程序員弱化掉可測性這個關注點(也可能是我個人理解和實踐不夠正確)。
4.復雜場景的調(diào)試問題、更多可測性的實踐,需要再摸索下,也希望得到相關分享和指點。
