廉頗老矣,尚能飯否? --辛棄疾git
瞭解設計模式的人應該都多少據說過MVC模式。 嚴格意義上來講,「MVC模式」是一個僞概念,由於MVC並不屬於設計模式,至少不屬於GoF的23種設計模式之一,而更像是一個設計模式的結合體:V和C之間會實現觀察者模式,M內部會實現單例模式,C在派發任務時會實現Command模式。 不得不說,MVC模式對軟件的高可擴展性和高可維護性作出了巨大的貢獻,這也使得MVC模式成爲不少中等規模甚至大規模軟件的經常使用框架,且經歷了20餘年仍舊在軟件開發領域流行並通用,足可見MVC模式的經典。 可是傳統MVC模式真的那麼完美嗎?github
讓咱們一個個來講。 Controller:控制器,包含了項目的業務邏輯。可是也是被你們吐槽最多的一個,緣由就是不少人,或者說大多數人,習慣於什麼都往Controller裏寫,最後一個Controller超過1000行代碼是司空見慣的事。因此關於傳統MVC的第一個痛點就是,Controller過於臃腫。編程
Model:模型,包含了項目的數據模型。MVC定義之初,Model是核心,旨在使得同一個Model能夠被複用到多個項目或者被複用到同一個項目的不一樣模塊之中。可是在實際項目中,Model還承載着純Model層內部的運算的工做,可是運算部分會項目的不一樣而有所區別,所以與項目的適配反而成爲了Model可複用的枷鎖。因此關於傳統MVC的第二個痛點就是,Model變得不可複用。設計模式
View:視圖,包含了項目全部的UI組件。視圖自己沒有什麼好被你們詬病的,可是因爲MVC中對於View和Controller界限的模糊界定形成了使用者在寫代碼的時候會以爲這部分代碼放在View或者Controller裏均可以的狀況。例如事件的處理,組件的組合等。因此關於傳統MVC的第三個痛點就是,View概念的模糊。bash
既然上文說的是傳統MVC,那麼能夠斷定PureMVC是一個新型MVC。架構
其實PureMVC只是相對於傳統MVC(20年陳釀)來講「新」一些而已,由於PureMVC今年也已經有10年的歷史了。mvc
PureMVC是一款基於MVC的開源框架,最初是爲基於ActionScript3的Flash,RIA程序開發的,後來被移植到16種語言平臺上。app
PureMVC分爲標準版本和多核版本,後者爲程序的模塊化開發提供了支持。本文以標準版爲例分析PureMVC。框架
Proxy(模式),提供了一個一個包裝器或一箇中介被客戶端調用,從而達到去訪問在場景背後的真實對象。Proxy模式能夠方便的將操做轉給真實對象,或者提供額外的邏輯。異步
在PureMVC中,Model保存了對Proxy對象的引用,Proxy去操做具體的數據模型(Data Object)。也就是說,Proxy管理Data Object以及對Data Object的訪問。
Mediator(模式),定義了一種封裝對象之間交互的中介。這種設計模式被認爲是行爲模式由於它能夠改變模式的運行行爲。
正如定義裏所說,PureMVC中,View只關心UI,具體的對對象的操做由Mediator來管理,包括添加事件監聽,發送或接受Notification,改變組件狀態等。這也解決了視圖與視圖控制邏輯的分離。
Command(模式),是一種行爲設計模式,這種模式下全部動做或者行爲所需信息被封裝到一個對象以內。Command模式解耦了發送者與接收者之間的聯繫。 在PureMVC中,Controller保存了全部Command的映射。Command是無狀態且惰性的,只有在須要的時候才被建立。
與傳統MVC模式不用的是,PureMVC中對於Model,View,Controller的調用是基於Facade模式的。 Facade模式,對應了GoF中的Facade模式,是一種將複雜且龐大的內部實現暴露爲一個簡單接口的設計模式,例如對大型類庫的封裝。
在PureMVC中,Facade是與核心層(Model,View,Controller)進行通訊的惟一接口,目的是簡化開發複雜度。實際編碼過程當中,不須要手動實現這三類文件,Facade類在構造方法中已經包含了對這三類單例的構造。
View層的Mediator能夠和Model層的Proxy進行互相訪問,可是PureMVC設計之初是但願只有View依賴於Model,反之不成立。也就是View能夠知道Model層有什麼,可是Model層不須要知道View的任何內容。Mediator訪問數據能夠直接經過Proxy來完成,可是若是要對Proxy具體的內容進行加工,必需要經過Controller的Command來完成,這有助於實現View和Model之間的鬆散耦合。
如上文所說,Proxy最好不要直接調用Mediator來通知它請求完成,而是在異步取到數據以後,經過Notification來進行通知。Proxy只發送通知,不該該監聽通知,由於Proxy屬於Model層,不該該知道View層的狀態變化。固然,Proxy應當對外提供數據變動的接口。
Command的實例化與執行只能由Controller來作。做爲控制邏輯的執行體,Command有權拿到Proxy和Mediator的對象,並進行值加工,最後會將結果經過Notification發送給其它Command或者Mediator。
你可能會遇到這個問題:某段邏輯究竟是應當放在Proxy(Model)裏,仍是應該放在Command(Controller)裏? 其實這個問題能夠引伸爲業務邏輯與域邏輯的區別。
所以,業務邏輯理所固然應該放在Command裏來完成,而域邏輯應當放在Proxy裏完成。
這裏以筆者實現的一個簡單的計算程序爲例來分解PureMVC。
這裏的關鍵點是實現startup方法和initializeController,示例以下:
ApplicationFacade.m
- (void)startup:(id)app
{
[self sendNotification:StartUp body:app];
}
複製代碼
具體的初始化方法放到了StartUpCommand中,包括建立視圖,註冊Proxy以及註冊Mediator:
StartUpCommand.m
- (void)execute:(id<INotification>)notification
{
UIWindow *appWindow = [notification body];
ViewController *viewController = [[ViewController alloc] init];
appWindow.rootViewController = viewController;
appWindow.backgroundColor = [UIColor whiteColor];
[appWindow makeKeyAndVisible];
// register mediators
[facade registerMediator:[ViewMediator withViewComponent:viewController]];
// register proxys
[facade registerProxy:[ElementProxy proxy]];
}
複製代碼
本例中只有一個View,負責UI顯示。當用戶點擊「=」時出發操做,此時內部將此事件拋到對應代理中,對應代碼以下:
ViewController.h
@protocol ViewControllerDelegate <NSObject>
- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB;
@end
ViewController.m
- (void)addTwoNumbers
{
if (self.delegate && [self.delegate respondsToSelector:@selector(addNumberA:andNumberB:)]) {
[self.delegate addNumberA:[self.inputA.text floatValue] andNumberB:[self.inputB.text floatValue]];
}
}
複製代碼
在對應Mediator中要關注四個方法:
- (void)onRegister
{
[self.viewComponent setDelegate:self];
}
複製代碼
- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB
方法。本例中,DataObject只保存業務相關的變量,numberA,numberB,result。 本例中業務邏輯因爲很簡單,所以Proxy只封裝了對DataObject中變量的存取以及變量是否能夠操做的判斷。
ElementProxy.h
@interface ElementProxy : Proxy
- (void)setNumberA:(NSNumber *)numberA andNumberB:(NSNumber *)numberB;
- (NSNumber *)getNumberA;
- (NSNumber *)getNumberB;
- (void)setResult:(NSNumber *)result;
- (NSNumber *)getResult;
- (BOOL)canOperate;
@end
複製代碼
在PureMVC中,Controller已經在Facade的實例化中被隱式建立好,所以只須要建立對應的Command而且在Facade中進行註冊便可。
- (void)initializeController
{
[super initializeController];
[self registerCommand:StartUp commandClassRef:[StartUpCommand class]];
[self registerCommand:AddTwoNumbers commandClassRef:[AddTwoNumbersCommand class]];
}
複製代碼
對應Command的邏輯:
- (void)execute:(id<INotification>)notification
{
ElementProxy *elmentProxy = (ElementProxy *)[facade retrieveProxy:[ElementProxy NAME]];
NSNumber *numberA = elmentProxy.getNumberA;
NSNumber *numberB = elmentProxy.getNumberB;
NSNumber *result = [NSNumber numberWithFloat:([numberA floatValue]+ [numberB floatValue])];
[elmentProxy setResult:result];
[facade sendNotification:ShowResult];
}
複製代碼
回到文章的開頭,PureMVC到底如何解決了傳統MVC的三個痛點?
根據PureMVC的最佳實踐,Controller實體不須要單獨實現,且Controller內部將每個操做分割爲一個個Command,這從根本上解決了Controller愈來愈臃腫的問題,強制用戶將Controller裏每個操做細粒度化,使得代碼可讀性更強,維護性更高。
PureMVC中,與域相關的邏輯和接口由Proxy來負責,後續的添加和修改接口只在Proxy中完成。而DataObject是徹底對業務進行數據建模而產生的數據模型,與業務沒有絲毫的關係,所以也保證了高可移植性。
PureMVC規定了ViewComponent只負責UI的繪製,而其餘事情,包括事件的綁定通通交給Mediator來作。這也就避免了ViewComponent內部代碼定義模糊,更不會和Controller的代碼進行混淆。
##後記 記得第一次接觸PureMVC是在2009年左右,當時剛接觸編程沒多久的我讀着師兄的解讀一遍一遍的用actionScript進行實現,雖然沒徹底懂爲何有那些模塊,模塊之間爲何要那樣通訊,可是開始體會到框架的魅力和使用的樂趣。 隨着工做年限的增長和編程經驗的增加,愈來愈以爲這款框架固化了我不少正確的觀念,這些觀念漸漸的讓我對以後的編程有了正確的感受,因此PureMVC能夠稱得上是我框架方面的啓蒙老師。 可是很遺憾的是,隨着Adobe Flash平臺的沒落,這款在ActionScript上廣爲流行的框架也變的風光再也不,即使它已經被翻譯成16種程序語言。 因此我決定在時隔這麼久從新學習這個框架,將框架運用到簡單的例子中,解決在GitHub上沒有可運行的iOS版本PureMVC Demo的尷尬情景。(官方Demo還停留在iOS3.0上) 但願教師節這天,我能幫我這位老師彈彈塵土,讓更多的人從新關注到它。畢竟,好的框架值得任何一門語言來借鑑。
##Reference PureMVC Best Practise Facade Pattern: WikiPedia Mediator Pattern:WikiPedia Proxy Pattern:Wikipedia Command Pattern:WikiPedia