前言
在上一家公司做了幾個月的SDK開發(fā),那就涉及到“組件化”盐茎,當一個公司的業(yè)務和產(chǎn)品越來越多,越來越復雜的時候徙赢,就要考慮組件化了字柠。那今天就聊一聊組件化。
正文
那什么是組件化呢狡赐?
我想也許大多數(shù)都用過CocoaPods包管理工具募谎,我們pod下來的不同的功能庫其實就是一個組件,我的理解組件是指比較小的功能塊阴汇,這些組件不需要多少組件間通信数冬,沒什么依賴,也就不需要做什么其他處理搀庶,面向對象就能搞定拐纱。
一個 APP 有多個模塊,模塊之間會通信哥倔,互相調用秸架。例如微信讀書有 書籍詳情 想法列表 閱讀器 發(fā)現(xiàn)卡片 等等模塊,這些模塊會互相調用咆蒿,例如 書籍詳情要調起閱讀器和想法列表东抹,閱讀器要調起想法列表和書籍詳情,我們一般會push過去沃测。
看起來挺好缭黔,這樣做簡單明了,沒有多余的東西蒂破,項目初期推薦這樣快速開發(fā)馏谨,但到了項目越來越龐大,這種方式會有什么問題呢附迷?顯而易見惧互,每個模塊都離不開其他模塊哎媚,互相依賴粘在一起成為一坨:
這樣揉成一坨對測試/編譯/開發(fā)效率/后續(xù)擴展都有一些壞處,那怎么解開這一坨呢喊儡。很簡單拨与,按軟件工程的思路,下意識就會加一個中間層:
那么問題來了:
- Mediator 怎么去轉發(fā)組件間調用艾猜?
- 一個模塊只跟 Mediator 通信截珍,怎么知道另一個模塊提供了什么接口?
- 按上圖的畫法箩朴,模塊和 Mediator 間互相依賴岗喉,怎樣破除這個依賴?
解決方案
對于前兩個問題炸庞,最直接的反應就是在 Mediator 直接提供接口钱床,調用對應模塊的方法:
Mediator.m 中間層
.#import "BookDetailComponent.h"
.#import "ReviewComponent.h"
@implementation Mediator
+ (UIViewController *)BookDetailComponent_viewController:(NSString *)bookId {
return [BookDetailComponent detailViewController:bookId];
}
+ (UIViewController *)ReviewComponent_viewController:(NSString *)bookId reviewType:(NSInteger)type {
return [ReviewComponent reviewViewController:bookId type:type];
}
@end
//BookDetailComponent 組件
#import "Mediator.h"
#import "WRBookDetailViewController.h"
@implementation BookDetailComponent
+ (UIViewController *)detailViewController:(NSString *)bookId {
WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:bookId];
return detailVC;
}
@end
//ReviewComponent 組件
#import "Mediator.h"
#import "WRReviewViewController.h"
@implementation ReviewComponent
+ (UIViewController *)reviewViewController:(NSString *)bookId type:(NSInteger)type {
UIViewController *reviewVC = [[WRReviewViewController alloc] initWithBookId:bookId type:type];
return reviewVC;
}
@end
然后在閱讀模塊里:
//WRReadingViewController.m
#import "Mediator.h"
@implementation WRReadingViewController
- (void)gotoDetail:(NSString *)bookId {
UIViewController *detailVC = [Mediator BookDetailComponent_viewControllerForDetail:bookId];
[self.navigationController pushViewController:detailVC];
UIViewController *reviewVC = [Mediator ReviewComponent_viewController:bookId type:1];
[self.navigationController pushViewController:reviewVC];
}
@end
這就是一開始架構圖的實現(xiàn),看起來顯然這樣做并沒有什么好處埠居,依賴關系并沒有解除查牌,Mediator 依賴了所有模塊,而調用者又依賴 Mediator滥壕,最后還是一坨互相依賴纸颜,跟原來沒有 Mediator 的方案相比除了更麻煩點其他沒區(qū)別。
那怎么辦呢绎橘。
怎樣讓Mediator解除對各個組件的依賴胁孙,同時又能調到各個組件暴露出來的方法?對于OC有一個法寶可以做到称鳞,就是runtime反射調用:
//Mediator.m
@implementation Mediator
+ (UIViewController *)BookDetailComponent_viewController:(NSString *)bookId {
Class cls = NSClassFromString(@"BookDetailComponent");
return [cls performSelector:NSSelectorFromString(@"detailViewController:") withObject:@{@"bookId":bookId}];
}
+ (UIViewController *)ReviewComponent_viewController:(NSString *)bookId type:(NSInteger)type {
Class cls = NSClassFromString(@"ReviewComponent");
return [cls performSelector:NSSelectorFromString(@"reviewViewController:") withObject:@{@"bookId":bookId, @"type": @(type)}];
}
@end
這下 Mediator 沒有再對各個組件有依賴了涮较,你看已經(jīng)不需要 #import 什么東西了,對應的架構圖就變成:
這樣就完全解耦了冈止,但這樣做的問題是:
- 調用者寫起來很惡心狂票,代碼提示都沒有,每次調用寫一坨熙暴。
- runtime方法的參數(shù)個數(shù)和類型限制闺属,導致只能每個接口都統(tǒng)一傳一個 NSDictionary。這個 NSDictionary里的key value是什么不明確周霉,需要找個地方寫文檔說明和查看掂器。
- 編譯器層面不依賴其他組件,實際上還是依賴了诗眨,直接在這里調用唉匾,沒有引入調用的組件時就掛了
把它移到Mediator后:
- 調用者寫起來不惡心,代碼提示也有了匠楚。
- 參數(shù)類型和個數(shù)無限制巍膘,由 Mediator 去轉就行了,組件提供的還是一個 NSDictionary 參數(shù)的接口芋簿,但在Mediator 里可以提供任意類型和個數(shù)的參數(shù)峡懈,像上面的例子顯式要求參數(shù) NSString *bookId 和 NSInteger type。
- Mediator可以做統(tǒng)一處理与斤,調用某個組件方法時如果某個組件不存在肪康,可以做相應操作,讓調用者與組件間沒有耦合撩穿。
到這里磷支,基本上能解決我們的問題:各組件互不依賴,組件間調用只依賴中間件Mediator食寡,Mediator不依賴其他組件雾狈。接下來就是優(yōu)化這套寫法,有兩個優(yōu)化點:
- Mediator 每一個方法里都要寫 runtime 方法抵皱,格式是確定的善榛,這是可以抽取出來的。
- 每個組件對外方法都要在 Mediator 寫一遍呻畸,組件一多 Mediator 類的長度是恐怖的移盆。
優(yōu)化后就成了 casa 的方案,target-action 對應第一點伤为,target就是class咒循,action就是selector,通過一些規(guī)則簡化動態(tài)調用绞愚。Category 對應第二點剑鞍,每個組件寫一個 Mediator 的 Category,讓 Mediator 不至于太長爽醋。這里有個demo
總結起來就是蚁署,組件通過中間件通信,中間件通過 runtime 接口解耦蚂四,通過 target-action 簡化寫法光戈,通過 category 感官上分離組件接口代碼。
參考:組件化架構漫談