PureMVC--一款多平台MVC框架


官網:http://puremvc.org/

下載:https://github.com/PureMVC/puremvc-csharp-multicore-framework/tree/1.1.0

API文檔:http://puremvc.org/pages/docs/CSharp/multicore/

引子

了解設計模式的人應該都多少聽說過MVC模式。
嚴格意義上來說,“MVC模式”是一個偽概念,因為MVC並不屬於設計模式,至少不屬於GoF的23種設計模式之一,而更像是一個設計模式的結合體:V和C之間會實現觀察者模式,M內部會實現單例模式,C在派發任務時會實現Command模式。
不得不說,MVC模式對軟件的高可擴展性和高可維護性做出了巨大的貢獻,這也使得MVC模式成為很多中等規模甚至大規模軟件的常用框架,且經歷了20余年仍舊在軟件開發領域流行並通用,足可見MVC模式的經典。
但是傳統MVC模式真的那么完美嗎?

傳統MVC的痛點

讓我們一個個來說。
Controller:控制器,包含了項目的業務邏輯。但是也是被大家吐槽最多的一個,原因就是很多人,或者說大多數人,習慣於什么都往Controller里寫,最后一個Controller超過1000行代碼是司空見慣的事。所以關於傳統MVC的第一個痛點就是,Controller過於臃腫。

Model:模型,包含了項目的數據模型。MVC定義之初,Model是核心,旨在使得同一個Model可以被復用到多個項目或者被復用到同一個項目的不同模塊之中。但是在實際項目中,Model還承載着純Model層內部的運算的工作,但是運算部分會項目的不同而有所區別,因此與項目的適配反而成為了Model可復用的枷鎖。所以關於傳統MVC的第二個痛點就是,Model變得不可復用。

View:視圖,包含了項目所有的UI組件。視圖本身沒有什么好被大家詬病的,但是由於MVC中對於View和Controller界限的模糊界定造成了使用者在寫代碼的時候會覺得這部分代碼放在View或者Controller里都可以的情況。例如事件的處理,組件的組合等。所以關於傳統MVC的第三個痛點就是,View概念的模糊。

PureMVC

既然上文說的是傳統MVC,那么可以判定PureMVC是一個新型MVC。

其實PureMVC只是相對於傳統MVC(20年陳釀)來說“新”一些而已,因為PureMVC今年也已經有10年的歷史了。

PureMVC是一款基於MVC的開源框架,最初是為基於ActionScript3的Flash,RIA程序開發的,后來被移植到16種語言平台上

PureMVC分為標准版本和多核版本,后者為程序的模塊化開發提供了支持。本文以標准版為例分析PureMVC。

PureMVC的MVC


PureMVC架構圖


在PureMVC實現的MVC模式中,MVC分別由三個單例模式來管理,三者成為PureMVC的核心層。

Model與Proxy

Proxy(模式),提供了一個一個包裝器或一個中介被客戶端調用,從而達到去訪問在場景背后的真實對象。Proxy模式可以方便的將操作轉給真實對象,或者提供額外的邏輯。

在PureMVC中,Model保存了對Proxy對象的引用,Proxy去操作具體的數據模型(Data Object)。也就是說,Proxy管理Data Object以及對Data Object的訪問。

View與Mediator

Mediator(模式),定義了一種封裝對象之間交互的中介。這種設計模式被認為是行為模式因為它可以改變模式的運行行為。

正如定義里所說,PureMVC中,View只關心UI,具體的對對象的操作由Mediator來管理,包括添加事件監聽,發送或接受Notification,改變組件狀態等。這也解決了視圖與視圖控制邏輯的分離。

Controller與Command

Command(模式),是一種行為設計模式,這種模式下所有動作或者行為所需信息被封裝到一個對象之內。Command模式解耦了發送者與接收者之間的聯系。
在PureMVC中,Controller保存了所有Command的映射。Command是無狀態且惰性的,只有在需要的時候才被創建。

Facade

與傳統MVC模式不用的是,PureMVC中對於Model,View,Controller的調用是基於Facade模式的。
Facade模式,對應了GoF中的Facade模式,是一種將復雜且龐大的內部實現暴露為一個簡單接口的設計模式,例如對大型類庫的封裝。

在PureMVC中,Facade是與核心層(Model,View,Controller)進行通信的唯一接口,目的是簡化開發復雜度。實際編碼過程中,不需要手動實現這三類文件,Facade類在構造方法中已經包含了對這三類單例的構造。

PureMVC各層之間的交互

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。

業務邏輯 VS 域邏輯

你可能會遇到這個問題:某段邏輯到底是應當放在Proxy(Model)里,還是應該放在Command(Controller)里?
其實這個問題可以引申為業務邏輯與域邏輯的區別。

  • 業務邏輯
    指的是那些需要協調Model與View的邏輯。
  • 域邏輯
    指的是僅僅是針對數據模型的操作,不論是對於客戶端還是對於服務端,不論是同步的操作還是異步的操作。

因此,業務邏輯理所當然應該放在Command里來完成,而域邏輯應當放在Proxy里完成。

案例分析

這里以筆者實現的一個簡單的計算程序為例來分解PureMVC。


PureMVC Demo

創建Facade

這里的關鍵點是實現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]]; }

創建ViewComponent和對應Mediator

本例中只有一個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中要關注四個方法:

  • onRegister,負責給對應的ViewComponent添加事件或代理:
- (void)onRegister { [self.viewComponent setDelegate:self]; }
  • listNotificationInterests,像Facade注冊Mediator關心的Notification列表。當向Facade發送Notification時會遍歷每一個Mediator的InterestList,會根據這個列表進行事件響應。
  • handleNotification,一旦向Facade發送的事件命中listNotificationInterests列表則會回調到這個函數,此處應放接收事件后的邏輯。
  • 實現對應ViewComponent的事件或者代理方法。本例中為- (void)addNumberA:(CGFloat)numberA andNumberB:(CGFloat)numberB方法。

創建DataObject和對應Proxy

本例中,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

創建Controller和Command

在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]; }

模塊間交互順序圖


Sequence Diagram


如圖所示,在接收到外部事件后,viewCompoent第一時間將事件拋到ViewMediator中,后者將事件相關變量存到Proxy進而存到了VO,也就是DataObject里。之后ViewMediator發送需要操作的命令通知addNumberNotification,Facade將此通知分配給實現注冊好的addNumberCommand。Command從Proxy拿到相關變量后,運算,並將結果寫到Proxy中,最后向Facade發送可以顯示結果的通知showResultNotification。Facade將此通知轉發給之前加過此通知到interest list的ViewMediator,Mediator從Proxy處取結果后把結果通過ViewComponent暴露出來的接口設置好,至此一次完整PureMVC交互流程完成。

猛回頭

回到文章的開頭,PureMVC到底如何解決了傳統MVC的三個痛點?

Controller將操作邏輯細化為Command

根據PureMVC的最佳實踐,Controller實體不需要單獨實現,且Controller內部將每一個操作分割為一個個Command,這從根本上解決了Controller越來越臃腫的問題,強制用戶將Controller里每一個操作細粒度化,使得代碼可讀性更強,維護性更高。

Proxy負責域邏輯,DataObject負責數據模型

PureMVC中,與域相關的邏輯和接口由Proxy來負責,后續的添加和修改接口只在Proxy中完成。而DataObject是完全對業務進行數據建模而產生的數據模型,與業務沒有絲毫的關系,因此也保證了高可移植性。

ViewComponent只關注UI,其余的交給Mediator

PureMVC規定了ViewComponent只負責UI的繪制,而其他事情,包括事件的綁定統統交給Mediator來做。這也就避免了ViewComponent內部代碼定義模糊,更不會和Controller的代碼進行混淆。

后記

記得第一次接觸PureMVC是在2009年左右,當時剛接觸編程沒多久的我讀着師兄的解讀一遍一遍的用actionScript進行實現,雖然沒完全懂為什么有那些模塊,模塊之間為什么要那樣通信,但是開始體會到框架的魅力和使用的樂趣。
隨着工作年限的增加和編程經驗的增長,越來越覺得這款框架固化了我很多正確的觀念,這些觀念漸漸的讓我對之后的編程有了正確的感覺,所以PureMVC可以稱得上是我框架方面的啟蒙老師。
但是很遺憾的是,隨着Adobe Flash平台的沒落,這款在ActionScript上廣為流行的框架也變的風光不再,即便它已經被翻譯成16種程序語言。
所以我決定在時隔這么久重新學習這個框架,將框架運用到簡單的例子中,解決在GitHub上沒有可運行的iOS版本PureMVC Demo的尷尬情景。(官方Demo還停留在iOS3.0上)
希望教師節這天,我能幫我這位老師彈彈塵土,讓更多的人重新關注到它。畢竟,好的框架值得任何一門語言來借鑒。

本文涉及代碼地址

nimoment/PureMVC_practise

本文關聯文章

你真的了解MVC嗎

Reference

PureMVC Best Practise
Facade Pattern: WikiPedia
Mediator Pattern:WikiPedia
Proxy Pattern:Wikipedia
Command Pattern:WikiPedia

原文地址:http://www.jianshu.com/p/47deaced9eb3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM