記得以前所在的團隊,規(guī)模大了以后,客戶端團隊也被按照不同業(yè)務(wù)拆分到了不同的地方。當時,所有的代碼都集中在一個倉庫,團隊里面一百多號人,只要有一個人提交錯了,那么所有要更新代碼的人都得等到修復(fù)后提交。這樣一天下來,整個團隊的溝通和互相等待都浪費了大量時間。同時,開發(fā)完成要進行測試時,由于代碼相互耦合、歸屬不清,也影響到了問題排查的效率,并增加了溝通時間。
后來,我們痛定思痛,花了很大力氣去進行架構(gòu)整治,將業(yè)務(wù)完全解耦,將通用功能下沉,每個業(yè)務(wù)都是一個獨立的 Git 倉庫,每個業(yè)務(wù)都能夠生成一個 Pod 庫,最后再集成到一起。這樣經(jīng)過架構(gòu)整治以后,也就沒再出現(xiàn)過先前的窘境,開發(fā)效率也得到了極大的提升。由此可見,合理的架構(gòu)是多么得重要。
其實,這并不是個例。當業(yè)務(wù)需求量和團隊規(guī)模達到一定程度后,任何一款 App 都需要考慮架構(gòu)設(shè)計的合理性。
而談到架構(gòu)治理,就需要將老業(yè)務(wù)、老代碼按照新的架構(gòu)設(shè)計模式進行重構(gòu)。所以,架構(gòu)重構(gòu)考慮得越晚,重構(gòu)起來就越困難,快速迭代的需求開發(fā)和漫長的重構(gòu)之間的矛盾,如同在飛行的飛機上換引擎。及早考慮架構(gòu)設(shè)計就顯得尤為重要。
那么,如何設(shè)計一個能支持大規(guī)模 App 的架構(gòu)呢?接下來,我就和你說說這個話題。
蘋果官方推薦的 App 開發(fā)模式是 MVC,隨之衍生出其他很多類 MVC 的設(shè)計模式 MVP、MVVM、MVCS ,它們在不同程度上增強了視圖、數(shù)據(jù)的通信方式,使得邏輯、視圖、數(shù)據(jù)之間的通信更靈活、規(guī)整、易于擴展。在 App 浪潮初期,幾乎所有 App 采用的都是這種類 MVC 的結(jié)構(gòu)。原因在于,MVC 是很好的面向?qū)ο缶幊谭妒剑浅_m合個人開發(fā)或者小團隊開發(fā)。
但是,項目大了,人員多了以后,這種架構(gòu)就扛不住了。因為,這時候功能的量級不一樣了。一個大功能,會由多個功能合并而成,每個功能都成了一個獨立的業(yè)務(wù),團隊成員也會按照業(yè)務(wù)分成不同的團隊。此時,簡單的邏輯、視圖、數(shù)據(jù)劃分再也無法滿足 App 大規(guī)模工程化的需求。
所以,接下來我們就不得不考慮模塊粒度如何劃分、如何分層,以及多團隊如何協(xié)作這三個問題了。解決了這三個問題以后,我們就可以對模塊內(nèi)部做進一步優(yōu)化了。模塊久經(jīng)考驗后,就能成為通用功能對外部輸出,方便更多的團隊。
總的來說,架構(gòu)是需要演進的。如果項目規(guī)模大了還不演進,必然就會拖累業(yè)務(wù)的發(fā)展速度。
簡單架構(gòu)向大型項目架構(gòu)演進中,就需要解決三個問題,即:模塊粒度應(yīng)該如何劃分?如何分層?多團隊如何協(xié)作?而在這其中,模塊粒度的劃分是架構(gòu)設(shè)計中非常關(guān)鍵的一步。同時,這也是一個細活,我們最好可以在不同階段采用不同的粒度劃分模塊。現(xiàn)在,我們就帶著這三個問題繼續(xù)往下看吧。
大項目、多人、多團隊架構(gòu)思考
接下來,我先和你說下模塊粒度應(yīng)該怎么劃分的問題。
首先,項目規(guī)模變大后,模塊劃分必須遵循一定的原則。如果模塊劃分規(guī)則不規(guī)范、不清晰,就會導(dǎo)致代碼耦合嚴重的問題,并加大架構(gòu)重構(gòu)的難度。這些問題主要表現(xiàn)在:
業(yè)務(wù)需求不斷,業(yè)務(wù)開發(fā)不能停。重新劃分模塊的工作量越大,成本越高,重構(gòu)技改需求排上日程的難度也就越大。
老業(yè)務(wù)代碼年久失修,沒有注釋,修改起來需要重新梳理邏輯和關(guān)系,耗時長。
其次,我們需要搞清楚模塊的粒度采用什么標準進行劃分,也就是要遵循的原則是什么。
對于 iOS 這種面向?qū)ο缶幊痰拈_發(fā)模式來說,我們應(yīng)該遵循以下五個原則,即 SOLID 原則。
單一功能原則:對象功能要單一,不要在一個對象里添加很多功能。
開閉原則:擴展是開放的,修改是封閉的。
里氏替換原則:子類對象是可以替代基類對象的。
接口隔離原則:接口的用途要單一,不要在一個接口上根據(jù)不同入?yún)崿F(xiàn)多個功能。
依賴反轉(zhuǎn)原則:方法應(yīng)該依賴抽象,不要依賴實例。iOS 開發(fā)就是高層業(yè)務(wù)方法依賴于協(xié)議。
同時,遵守這五個原則是開發(fā)出容易維護和擴展的架構(gòu)的基礎(chǔ)。
最后,我們需要選擇合適的粒度。切記,大型項目的模塊粒度過大或者過小都不合適。
其中,組件可以認為是可組裝的、獨立的業(yè)務(wù)單元,具有高內(nèi)聚,低耦合的特性,是一種比較適中的粒度。就像用樂高拼房子一樣,每個對象就是一塊小積木。一個組件就是由一塊一塊的小積木組成的有單一功能的組合,比如門、柱子、煙囪。
在我看來,iOS 開發(fā)中的組件,不是 UI 的控件,也不是 ViewController 這種大 UI 和功能的集合。因為,UI 控件的粒度太小,而頁面的粒度又太大。iOS 組件,應(yīng)該是包含 UI 控件、相關(guān)多個小功能的合集,是一種粒度適中的模塊。
并且,采用組件的話,對于代碼邏輯和模塊間的通信方式的改動都不大,完成老代碼切換也就相對容易些。我們可以先按照物理劃分,也就是將多個相同功能的類移動到同一個文件夾下,然后做成 CocoaPods 的包進行管理。
但是,僅做到這一步還不夠,因為功能模塊之間的耦合還是沒有被解除。如果沒有解除耦合關(guān)系的話,不同功能的開發(fā)還是沒法獨立開來,勉強開發(fā)完成后的影響范圍評估也難以確定。
所以接下來,我們就需要重新梳理組件之間的邏輯關(guān)系,進行改造。
但是,組件解耦并不是說要求每個組件間都沒有耦合,組件間也需要有上下層依賴的關(guān)系。組件間的上下層關(guān)系劃分清楚了,就會容易維護和管理。而對于組件間如何分層這個問題,我認為層級最多不要超過三個,你可以這么設(shè)置:
底層可以是與業(yè)務(wù)無關(guān)的基礎(chǔ)組件,比如網(wǎng)絡(luò)和存儲等;
中間層一般是通用的業(yè)務(wù)組件,比如賬號、埋點、支付、購物車等;
最上層是迭代業(yè)務(wù)組件,更新頻率最高。
這樣的三層結(jié)構(gòu),尤其有利于多個團隊分別開發(fā)維護。比如,一開始有兩個業(yè)務(wù)團隊 A 和 B,他們在開發(fā)時既有通用的功能、賬號、埋點、個人頁等,也有專有的業(yè)務(wù)功能模塊,每個功能都是一個組件。
這樣,新創(chuàng)建的業(yè)務(wù)團隊 C,就能非常輕松地使用團隊 A 和 B 開發(fā)出的通用組件。而且,如果兩個業(yè)務(wù)團隊有相同功能時,對相應(yīng)的功能組件進行簡單改造后,也能同時適用于兩個業(yè)務(wù)團隊。
但是,我認為不用把所有的功能都做成組件,只有那些會被多個業(yè)務(wù)或者團隊使用的功能模塊才需要做成組件。因為,改造成組件也是需要時間成本的,很少有公司愿意完全停下業(yè)務(wù)去進行重構(gòu),而一旦決定某業(yè)務(wù)功能模塊要改成組件,就要抓住機會,嚴格按照 SOLID 原則去改造組件,因為返工和再優(yōu)化的機會可能不會再有。
多團隊之間如何分工?
在代碼層面,我們通過組件化解決了大項目、多人、多團隊架構(gòu)的問題,但是架構(gòu)問題還涉及到團隊人員結(jié)構(gòu)上的架構(gòu)。當公司或者集團的 App 多了后,相應(yīng)的團隊也就多了,為了能夠讓產(chǎn)品快速迭代和穩(wěn)定發(fā)展,也需要一個合理的團隊結(jié)構(gòu)。在我看來,這個合理的團隊結(jié)構(gòu)應(yīng)該是這樣的:
首先,需要一個專門的基建團隊,負責業(yè)務(wù)無關(guān)的基礎(chǔ)功能組件和業(yè)務(wù)相關(guān)通用業(yè)務(wù)組件的開發(fā)。
然后,每個業(yè)務(wù)都由一個專門的團隊來負責開發(fā)。業(yè)務(wù)可以按照功能耦合度來劃分,耦合度高的業(yè)務(wù)可以劃分成單獨的業(yè)務(wù)團隊。
基建團隊人員應(yīng)該是流動的,從業(yè)務(wù)團隊里來,再回到業(yè)務(wù)團隊中去。這么設(shè)計是因為業(yè)務(wù)團隊和基建團隊的邊界不應(yīng)該非常明顯,否則就會出現(xiàn)基建團隊埋頭苦干,結(jié)果可能是做得過多、做得不夠,或著功能不好用的問題,造成嚴重的資源浪費。
總結(jié)來講,我想說的是團隊分工要靈活,不要把人員隔離固化了,否則各干各的,做的東西相互都不用。核心上,團隊分工還是要圍繞著具體業(yè)務(wù)進行功能模塊提煉,去解決重復(fù)建設(shè)的問題,在這個基礎(chǔ)上把提煉出的模塊做精做扎實。否則,拉一幫子人臆想出來的東西,無人問津,那就是把自己架空了。
我心目中好的架構(gòu)是什么樣的?
現(xiàn)在,我們已經(jīng)可以從代碼內(nèi)外來分析 App 開發(fā)的架構(gòu)設(shè)計了,但也只是會分析了而已,腦海中并沒有明確好的架構(gòu)是什么樣的,也不知道具體應(yīng)該怎么設(shè)計。接下來,我們就帶著這兩個問題繼續(xù)看下面的內(nèi)容。
組件化是解決項目大、人員多的一種很好的手段,這在任何公司或團隊都是沒有歧義的。組件間關(guān)系協(xié)調(diào)卻沒有固定的標準,協(xié)調(diào)的優(yōu)劣,成為了衡量架構(gòu)優(yōu)劣的一個基本標準。所以在實踐中,一般分為了協(xié)議式和中間者兩種架構(gòu)設(shè)計方案。
協(xié)議式架構(gòu)設(shè)計主要采用的是協(xié)議式編程的思路:在編譯層面使用協(xié)議定義規(guī)范,實現(xiàn)可在不同地方,從而達到分布管理和維護組件的目的。這種方式也遵循了依賴反轉(zhuǎn)原則,是一種很好的面向?qū)ο缶幊痰膶嵺`。
但是,這個方案的缺點也很明顯,主要體現(xiàn)在以下兩個方面:
由于協(xié)議式編程缺少統(tǒng)一調(diào)度層,導(dǎo)致難于集中管理,特別是項目規(guī)模變大、團隊變多的情況下,架構(gòu)管控就會顯得越來越重要。
協(xié)議式編程接口定義模式過于規(guī)范,從而使得架構(gòu)的靈活性不夠高。當需要引入一個新的設(shè)計模式來開發(fā)時,我們就會發(fā)現(xiàn)很難融入到當前架構(gòu)中,缺乏架構(gòu)的統(tǒng)一性。
雖然協(xié)議式架構(gòu)有這兩方面的局限性,但由于其簡單易用的特點依然被很多公司采用。
另一種常用的架構(gòu)形式是中間者架構(gòu)。它采用中間者統(tǒng)一管理的方式,來控制 App 的整個生命周期中組件間的調(diào)用關(guān)系。同時,iOS 對于組件接口的設(shè)計也需要保持一致性,方便中間者統(tǒng)一調(diào)用。
中間者架構(gòu)如下圖所示:
可以看到,拆分的組件都會依賴于中間者,但是組間之間就不存在相互依賴的關(guān)系了。由于其他組件都會依賴于這個中間者,相互間的通信都會通過中間者統(tǒng)一調(diào)度,所以組件間的通信也就更容易管理了。在中間者上也能夠輕松添加新的設(shè)計模式,從而使得架構(gòu)更容易擴展。
在我看來,好的架構(gòu)一定是健壯的、靈活的。中間者架構(gòu)的易管控帶來的架構(gòu)更穩(wěn)固,易擴展帶來的靈活性,所以我認為中間者這種架構(gòu)設(shè)計模式是非常值得推薦的。casatwy 以前設(shè)計了一個 CTMediator 就是按照中間者架構(gòu)思路設(shè)計的。你可以在GitHub上看到它的內(nèi)容。
CTMediator 使用的是運行時解耦,接下來我就通過開源的 CTMediator 代碼,和你分享下如何使用運行時技術(shù)來解耦。解耦核心方法如下所示:
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
? ? NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
? ? // generate target
? ? NSString *targetClassString = nil;
? ? if (swiftModuleName.length > 0) {
? ? ? ? targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
? ? } else {
? ? ? ? targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
? ? }
? ? NSObject *target = self.cachedTarget[targetClassString];
? ? if (target == nil) {
? ? ? ? Class targetClass = NSClassFromString(targetClassString);
? ? ? ? target = [[targetClass alloc] init];
? ? }
? ? // generate action
? ? NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
? ? SEL action = NSSelectorFromString(actionString);
? ? if (target == nil) {
? ? ? ? // 這里是處理無響應(yīng)請求的地方之一,這個 demo 做得比較簡單,如果沒有可以響應(yīng)的 target,就直接 return 了。實際開發(fā)過程中是可以事先給一個固定的 target 專門用于在這個時候頂上,然后處理這種請求的
? ? ? ? [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
? ? ? ? return nil;
? ? }
? ? if (shouldCacheTarget) {
? ? ? ? self.cachedTarget[targetClassString] = target;
? ? }
? ? if ([target respondsToSelector:action]) {
? ? ? ? return [self safePerformAction:action target:target params:params];
? ? } else {
? ? ? ? // 這里是處理無響應(yīng)請求的地方,如果無響應(yīng),則嘗試調(diào)用對應(yīng) target 的 notFound 方法統(tǒng)一處理
? ? ? ? SEL action = NSSelectorFromString(@"notFound:");
? ? ? ? if ([target respondsToSelector:action]) {
? ? ? ? ? ? return [self safePerformAction:action target:target params:params];
? ? ? ? } else {
? ? ? ? ? ? // 這里也是處理無響應(yīng)請求的地方,在 notFound 都沒有的時候,這個 demo 是直接 return 了。實際開發(fā)過程中,可以用前面提到的固定的 target 頂上的。
? ? ? ? ? ? [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
? ? ? ? ? ? [self.cachedTarget removeObjectForKey:targetClassString];
? ? ? ? ? ? return nil;
? ? ? ? }
? ? }
可以看出,指定了對象名和調(diào)用方法名,把參數(shù)封裝成字典傳進去就能夠直接調(diào)用該方法了。
但是,這種運行時直接硬編碼的調(diào)用方式也有些缺點,主要表現(xiàn)在兩個方面:
直接硬編碼的調(diào)用方式,參數(shù)是以 string 的方法保存在內(nèi)存里,雖然和將參數(shù)保存在 Text 字段里占用的內(nèi)存差不多,同時還可以避免.h 文件的耦合,但是其對代碼編寫效率的降低也比較明顯。
由于是在運行時才確定的調(diào)用方法,調(diào)用方式由 [obj method] 變成 [obj performSelector:@""]。這樣的話,在調(diào)用時就缺少類型檢查,是個很大的缺憾。因為,如果方法和參數(shù)比較多的時候,代碼編寫效率就會比較低。
這篇文章發(fā)出后 CTMediator 的作者 casatwy 找到了我,指出文章中提到的 CTMediator 的硬編碼和字典傳參這兩個缺點,實際上已經(jīng)被完美解決了。下面是 casatwy 的原話,希望可以對你有所幫助。
CTMediator 本質(zhì)就是一個方法,用來接收 target、action、params。由于 target、action 都是字符串,params 是字典,對于調(diào)用者來說十分不友好,因為調(diào)用者要寫字符串,而且調(diào)用的時候若是不看文檔,他也不知道這個字典里該塞什么東西。
所以實際情況中,調(diào)用者是不會直接調(diào)用 CTMediator 的方法的。那調(diào)用者怎么發(fā)起調(diào)用呢?通過響應(yīng)者給 CTMediator 做的 category 或者 extension 發(fā)起調(diào)用。
category 或 extension 以函數(shù)聲明的方式,解決了參數(shù)的問題。調(diào)用者看這個函數(shù)長什么樣子,就知道給哪些參數(shù)。在 category 或 extension 的方法實現(xiàn)中,把參數(shù)字典化,順便把 target、action 這倆字符串寫死在調(diào)用里。
于是,對于調(diào)用者來說,他就不必查文檔去看參數(shù)怎么給,也不必擔心 target、action 字符串是什么了。這個 category 是一個獨立的 Pod,由響應(yīng)者業(yè)務(wù)的開發(fā)給到。
所以,當一個工程師開發(fā)一個業(yè)務(wù)的時候,他會開發(fā)兩個 Pod,一個是 category Pod,一個是自己本身的業(yè)務(wù) Pod。這樣就完美解決了 CTMediator 它自身的缺點。
對于調(diào)用者來說,他不會直接依賴 CTMediator 去發(fā)起調(diào)用,而是直接依賴 category Pod 去發(fā)起調(diào)用的。這么一來,CTMediator 方案就完美了。
然后還有一點可能需要強調(diào):基于 CTMediator 方案的工程,每一個組件無所謂是 OC 還是 Swift,Pod 也無所謂是 category 還是 extension。也就是說,假設(shè)一個工程由 100 個組件組成,那可以是 50 個 OC、50 個 Swift。因為 CTMediator 抹去了不同語言的組件之間的隔閡,所以大家老的 OC 工程可以先應(yīng)用 CTMediator,把組件拆出來。然后新的業(yè)務(wù)來了,用 Swift 寫,等有空的時候再把老的 OC 改成 Swift,或者不改,都是沒問題的。
不過,解耦的精髓在于業(yè)務(wù)邏輯能夠獨立出來,并不是形式上的解除編譯上的耦合(編譯上解除耦合只能算是解耦的一種手段而已)。所以,在考慮架構(gòu)設(shè)計時,我們更多的還是需要在功能邏輯和組件劃分上做到同層級解耦,上下層依賴清晰,這樣的結(jié)構(gòu)才能夠使得上層組件易插拔,下層組件更穩(wěn)固。而中間者架構(gòu)模式更容易維護這種結(jié)構(gòu),中間者的易管控和易擴展性,也使得整體架構(gòu)能夠長期保持穩(wěn)健與活力。所以,中間者架構(gòu)就是我心目中好的架構(gòu)。
案例分享
明確了中間者架構(gòu)是我認為的好架構(gòu),那么如何體現(xiàn)其易管控和易擴展性呢?我通過一個案例來和你一起分析下。
這個例子的代碼,在 CTMediator 的基礎(chǔ)上進行了擴展,完整代碼請點擊這個 GitHub 鏈接 。
這個范例的主要組件類名和方法名,如下圖所示:
圖 2 主要的組件類名和方法名
可以看出,這個范例在中間者架構(gòu)的基礎(chǔ)上增加了對中間件、狀態(tài)機、觀察者、工廠模式的支持。同時,這個案例也在使用上做了些優(yōu)化,支持了鏈式調(diào)用,代碼如下:
self.dispatch(CdntAction.cls(@"PublishCom").mtd(@"viewInVC").pa(dic));
復(fù)制代碼
代碼中的 PublishCom 是組件類名,ViewInVC 是方法名。
下面說下中間件模式。在添加中間件的時候,我們只需要鏈式調(diào)用 addMiddlewareAction 就可以在方法執(zhí)行之前插入中間件。代碼如下:
self.middleware(@"PublishCom showEmergeView").addMiddlewareAction(CdntAction.clsmtd(@"AopLogCom emergeLog").pa(Dic.create.key(@"actionState").val(@"show").done));
復(fù)制代碼
這行代碼對字典參數(shù)也使用了鏈式方便參數(shù)的設(shè)置,使得字典設(shè)置更易于編寫。改變狀態(tài)使用 toSt 方法即可,狀態(tài)的維護和管理都在內(nèi)部實現(xiàn)。同一個方法不同狀態(tài)的實現(xiàn)只需要在命名規(guī)則上做文章即可,這也是得易于中間者架構(gòu)可以統(tǒng)一處理方法調(diào)用規(guī)則的特性。比如,confirmEmerge 方法在不同狀態(tài)下的實現(xiàn)代碼如下:
// 狀態(tài)管理
- (void)confirmEmerge_state_focusTitle:(NSDictionary *)dic {
? ? NSString *title = dic[@"title"];
? ? [self.fromAddressBt setTitle:title forState:UIControlStateNormal];
? ? self.fromAddressBt.tag = 1;
}
- (void)confirmEmerge_state_focusContent:(NSDictionary *)dic {
? ? NSString *title = dic[@"title"];
? ? [self.toAddressBt setTitle:title forState:UIControlStateNormal];
? ? self.toAddressBt.tag = 1;
}
- (void)confirmEmerge_state_focusPrice:(NSDictionary *)dic {
? ? NSString *title = dic[@"title"];
? ? [self.peopleBt setTitle:title forState:UIControlStateNormal];
? ? self.peopleBt.tag = 1;
}
復(fù)制代碼
可以看出,我們只需要在方法名后面加上“ _state _ 狀態(tài)名”,就能夠?qū)Σ煌瑺顟B(tài)進行不同實現(xiàn)了。
對于觀察者模式,使用起來也很簡單清晰。比如,發(fā)布文章這個事件需要兩個觀察者,一個執(zhí)行重置界面,一個檢查是否能夠發(fā)布,代碼如下:
// 觀察者管理 self.observerWithIdentifier(@"publishOk").addObserver(CdntAction.clsmtd(@"PublishCom reset")).addObserver(CdntAction.clsmtd(@"PublishCom checkPublish"));
復(fù)制代碼
這樣的寫法非常簡單清晰。在發(fā)布時,我們只需要執(zhí)行如下代碼:
[self notifyObservers:@"publishOk"];
復(fù)制代碼
觀察者方法添加后,也會記錄在內(nèi)部,它們的生命周期跟隨中間者的生命周期。
工廠模式的思路和狀態(tài)機類似,狀態(tài)機是對方法在不同狀態(tài)下的實現(xiàn),而工廠模式是對類在不同設(shè)置下的不同實現(xiàn)。由于有了中間者,我就可以在傳入類名后對其進行類似狀態(tài)機中方法名的處理,以便類的不同實現(xiàn)可以通過命名規(guī)則來完成。我們先看看中間者處理狀態(tài)機的代碼:
// State action 狀態(tài)處理
if (![self.p_currentState isEqual:@"init"]) {
? ? SEL stateMethod = NSSelectorFromString([NSString stringWithFormat:@"%@_state_%@:", sep[1], self.p_currentState]);
? ? if ([obj respondsToSelector:stateMethod]) {
? ? ? ? return [self executeMethod:stateMethod obj:obj parameters:parameters];
? ? }
}
復(fù)制代碼
可以看出當前的狀態(tài)會記錄在 p_currentState 屬性中,方法調(diào)用時方法名會和當前的狀態(tài)的命名拼接成一個完整的實現(xiàn)方法名來調(diào)用。中間者處理工廠模式的思路也類似,代碼如下:
// Factory
// 由于后面的執(zhí)行都會用到 class 所以需要優(yōu)先處理 class 的變形體
NSString *factory = [self.factories objectForKey:classStr];
if (factory) {
? ? classStr = [NSString stringWithFormat:@"%@_factory_%@", classStr, factory];
? ? classMethod = [NSString stringWithFormat:@"%@ %@", classStr, sep[1]];
}
復(fù)制代碼
可以看出,采用了中間者這種架構(gòu)設(shè)計思想后,架構(gòu)就具有了很高的擴展性和可管控性。所以,我推崇這種架構(gòu)設(shè)計思路。
小結(jié)
架構(gòu)的設(shè)計絕對不是要等到工程到了燃眉之急之時,再去環(huán)顧其他公司或團隊在用什么架構(gòu),然后拍腦袋拿一個過來,來次大重構(gòu)。好的架構(gòu),需要在業(yè)務(wù)開發(fā)過程中及早發(fā)現(xiàn)開發(fā)的痛點,進行有針對性的改良,不然就會和實際開發(fā)越走越遠。
比如,某個業(yè)務(wù)模塊的邏輯非常復(fù)雜、狀態(tài)有很多,這時我們就需要在架構(gòu)層面考慮如何處理會更方便,改動最小的支持狀態(tài)機模式,又或者在開始架構(gòu)設(shè)計時就多考慮如何將架構(gòu)設(shè)計的具有更高的易用性和可擴展性。
好的架構(gòu)是能夠在一定的規(guī)范內(nèi)同時支持高靈活度,這種度的把握是需要架構(gòu)師長期跟隨團隊開發(fā),隨著實際業(yè)務(wù)需求的演進進行分析和把控的。
在項目大了,人員多了的情況下,好的架構(gòu)一定是不簡單的,不通用的,但一定是接地氣的,這樣才能更適合自己的團隊,才能夠用得上。那些大而全,炫技,脫離業(yè)務(wù)開發(fā)需求的架構(gòu)是沒法落地的。
最后,我提點建議。我在面試應(yīng)聘者的時候,通常都會問他所負責項目的整體架構(gòu)是怎樣的。結(jié)果呢,很多人都只對自己負責的那攤子事兒說的溜,而回答所在項目整體情況時卻支支吾吾,也因此沒能面試成功。
所以,作為一名普通的開發(fā)者,除了日常需求開發(fā)和技術(shù)方案調(diào)研、設(shè)計外,你還需要了解自己所在項目的整體架構(gòu)是怎樣的,想想架構(gòu)上哪些地方是不夠好需要改進的,業(yè)界有哪些好的架構(gòu)思想是可以落地到自己項目中的。有了從項目整體上去思考的意識,你才能夠站在更高的視角上去思考問題。這,也是對架構(gòu)師的基本要求。
課后作業(yè)
架構(gòu)如何設(shè)計眾說紛紜,請你來說下你們項目目前架構(gòu)是怎樣的,并畫出你心中理想的架構(gòu)圖。