重構之PMVC框架在CocosCreator中的應用(一)
直接開始:
相對于PureMVC,可能MVC聽的更多一些。傳統(tǒng)經(jīng)典的MVC模型雖然將數(shù)據(jù),視圖組件和控制邏輯進行了分離,讓程序便于修改,更具有擴展性,靈活性,可重用性。高內聚,低耦合,但耦合性還是比較高,當然這個并不可以否認MVC模式對軟件的高可擴展性和高可維護性做出了巨大的貢獻。大致的說一下,MVC有三個核心概念:Controller:控制器;Model:模型;View:視圖。Controller包含了項目的業(yè)務邏輯,但是很多人習慣什么都往Controller里寫,一個Controller超過1000行代碼是經(jīng)??梢钥吹降氖虑椋灾劣贑ontroller過于臃腫,這樣不管是閱讀還是開發(fā),都不是很友好。Model作為mvc的核心,本意是想要同一個Model可以被復用到多個項目或者被復用到同一個項目的不同模塊之中,但是在實際開發(fā)中,Model還有一些承載著純Model層內部的運算的工作,但是運算部分會因為項目的不同而有所區(qū)別,因此與項目的適配反而會使Model變得不可復用。View包含了項目所有的UI組件。但是MVC中對于View和Controller界限的定義很模糊,開發(fā)者在寫代碼的時候會覺得這部分代碼放在View或者Controller里都可以:例如事件的處理,組件的組合等。所以關于傳統(tǒng)MVC的第三個痛點就是,View概念的模糊,耦合度還是很明顯。于是就有了今天要說的PureMVC。
PureMVC在傳統(tǒng)MVC基礎上做了許多的改進,通過結合多個“設計模式”的應用,讓耦合性變得更低,也變得更加的易用,在擴展性,靈活性,重用性方面也做得更好。先簡單說一下PMVC的基本架構,MVC依然是核心,MVC分別由三個單例模式來管理,在PureMVC中,Model,View,Controller是三個單例模式類,三者合稱為核心層或核心角色,換句話說就是Manager管理類,他們分別定義了字典用于保存引用,以及Register,Add,Remove,Retrieve等方法,將使用到的具體層(Model,View,Controller),保存到字典中進行統(tǒng)一管理,這樣,在我需要獲取某一個具體層時,我可以通過key直接訪問到它們。。官方也提供了架構圖:

pmvc遵循的原則是”改變一個不影響其它“,這也是為什么不能用傳統(tǒng)的,將數(shù)據(jù),視圖組件和控制邏輯耦合在一起的做的原因,如果僅是改變了一個UI組件的位置,不應該讓數(shù)據(jù)和控制邏輯也進行編譯,或者對控制邏輯的調整,不應該影響到數(shù)據(jù)部分。
在傳統(tǒng)的mvc框架中中,要在Contoller中,獲取View以及Model的對象,修改Mode,更新View,或是在View,要獲取Model,進行一些初始化或是修改的操作,在業(yè)務邏輯很多的情況下,Model,View,Controller之間的頻繁的調用就會非常多。
在PureMVC中,通過一個上層的接口來負責所有核心層(Model,View,Controller)的管理和操作,也就是Facade設計模式,即外觀設計模式。
外觀設計模式的定義如下:
"為一組子系統(tǒng)或是接口提供一個統(tǒng)一的界面,以簡化其復雜度,降低耦合性"
”Facade提供了與核心層通信的唯一接口,以簡化開發(fā)復雜度?!?/p>
這樣在Controller中,獲取View和Model,或者View中,獲取Model,均統(tǒng)一使用Facade進行管理。就降低了MVC三層之間的耦合性。對于使用者來說,只需要知道Facade類存在就可以。實際編碼過程中,不需要手動實現(xiàn)這三類文件,F(xiàn)acade類在構造方法中已經(jīng)包含了對這三類單例的構造,并把對它們的引用保存在成員變量。這樣把對核心層的操作都集中在 Facade,避免開發(fā)者直接操作核心層。事實上,F(xiàn)acade類應被當成抽象類, 永遠不被直接實例化,你應該具體編寫 Facade 的子類,添加或重寫 Facade 的方法來實現(xiàn)具體的應用。這個類命名為“ApplicationFacade”(當然,命名隨意)。它主要負責訪問和通知 Model、View 和 Controller 。即管理這三者。
從架構圖中可以看到:Model,View,Controller之間并沒有任何的直接通信。
pmvc通過一系列的設計模式來進一步降低耦合性。如圖中看到的,pmvc增加Proxy,Mediator和Command三個概念

Proxy即代理設計模式,“為其它對象提供代理以控制該對象的訪問”。
Proxy(模式)提供了一個一個包裝器或一個中介被客戶端調用,從而達到去訪問在場景背后的真實對象。Proxy模式可以方便的將操作轉給真實對象,或者提供額外的邏輯。在PureMVC中,Model保存了對Proxy對象的引用,Proxy去操作具體的數(shù)據(jù)模型(Data Object)。也就是說,Proxy管理Data Object以及對Data Object的訪問。
Model即數(shù)據(jù)(Data Object),游戲是基于數(shù)據(jù)驅動的,比如角色的數(shù)據(jù),包括HP,MP,金幣,等級,經(jīng)驗,技能等等。
也就是說,我們不會直接和Model通信,對Model的增刪改查均是通過Proxy來處理的。而且改模式也滿足”改變一個不影響其它“的原則。上圖中Model的箭頭只和Facade進行交互。旁邊的一眾Obj即Model,對應著Proxy,但并不是一個Model對應一個Proxy,如果是這樣,就太繁瑣了,比如一個模塊中,可能包括很多種不同的Model數(shù)據(jù),你可以定義多個不同的Model,但可以通過一個Proxy進行管理,這樣更方便。

Mediator(中介者設計模式):“用一個中介對象來封裝一系列的對象交互“(重交互, 強邏輯)
這種設計模式被認為是行為模式因為它可以改變模式的運行行為。正如定義里所說,PureMVC中,View只關心UI,具體的對對象的操作由Mediator來管理,包括添加事件監(jiān)聽,發(fā)送或接受Notification,改變組件狀態(tài)等。這也解決了視圖與視圖控制邏輯的分離,即使UI重建,也只是更改Mediator;View Component即UI上的各種控件,按鈕,列表,滾動條等等。因為 Mediator 也會經(jīng)常和 Proxy 交互,所以經(jīng)常在 Mediator 的構造方法中取得Proxy 實例的引用并保存在 Mediator 的屬性中,這樣避免頻繁的獲取 Proxy 實例帶來的性能開銷通常View和Mediator是一對一的關系,但有些View會相對復雜,有多個子UI組成,Mediator中也可以有多個View Component引用(同一功能的不同子UI)。但如果Mediator過于龐大,就要進行拆分,在拆分后的子模塊的 Mediator 里處理要比全部放在一起更好

Command(模式),是一種行為設計模式,這種模式下所有動作或者行為所需信息被封裝到一個對象之內。Command模式解耦了發(fā)送者與接收者之間的聯(lián)系。
在PureMVC中,Controller保存了所有Command的映射。Command是無狀態(tài)且惰性的,只有在需要的時候才被創(chuàng)建。對View或Mediator的修改也不會影響到Command本身。通過Facade頂層接口,可以在Proxy,Mediator,Command之間,相互訪問和通信。Command 可以獲取 Proxy 和Mediator對象并與之交互,發(fā)送 Notification,執(zhí)行其他的 Command。經(jīng)常用于復雜的或系統(tǒng)范圍的操作,如應用程序的“啟動”和“關閉”。應用程序的業(yè)務邏輯應該在這里實現(xiàn)。
再來看最開始的架構圖:

View層的Mediator可以和Model層的Proxy進行互相訪問,但是PureMVC設計之初是希望只有View依賴于Model,反之不成立。也就是View可以知道Model層有什么,但是Model層不需要知道View的任何內容。Mediator訪問數(shù)據(jù)可以直接通過Proxy來完成,但是如果要對Proxy具體的內容進行加工,必須要通過Controller的Command來完成,這有助于實現(xiàn)View和Model之間的松散耦合。也就是說Proxy最好不要直接調用Mediator來通知它請求完成,而是在異步取到數(shù)據(jù)之后,通過Notification來進行通知。Proxy只發(fā)送通知,不應該監(jiān)聽通知,因為Proxy屬于Model層,不應該知道View層的狀態(tài)變化。當然,Proxy應當對外提供數(shù)據(jù)變更的接口。Command的實例化與執(zhí)行只能由Controller來做。作為控制邏輯的執(zhí)行體,Command有權拿到Proxy和Mediator的對象,并進行值加工,最后會將結果通過Notification發(fā)送給其它Command或者Mediator。
關于邏輯可能會有這樣的問題:某段邏輯到底是應當放在Proxy(Model)里,還是應該放在Command(Controller)里?
其實這個問題可以引申為業(yè)務邏輯與域邏輯的區(qū)別。程序中的邏輯分為 Business Logic(業(yè)務邏輯)和 Domain Logic(域邏輯),首先需要知道這兩者之間的差別。
Business Logic(業(yè)務邏輯)要協(xié)調 Model 與視圖狀態(tài)(View)。
Model 通過使用 Proxy 來保證數(shù)據(jù)的完整性、一致性 。Proxy 集中程序的Domain Logic(域邏輯),并對外公布操作數(shù)據(jù)對象的 API。它封裝了所有對數(shù)據(jù)模型的操作,不管數(shù)據(jù)是客戶端還是服務器端的,對程序其他部分來說就是數(shù)據(jù)的訪問是同步還是異步的。
Mediator 和 Proxy 可以提供一些操作接口讓 Command 調用來管理 ViewComponent 和Model( Data Object),同時對 Command 隱藏具體操作的細節(jié)
pmvc的理論基本上就是這樣的,當然在實際應用中,還是要根據(jù)項目需求適當?shù)恼{整。
照著上面的架構圖理一下流程:
先到http://puremvc.org官網(wǎng)上下載一套TS的源碼:
打開dashboard創(chuàng)建一個新項目,并簡單創(chuàng)建一下項目結構,并把源碼放進去;
類似這樣:

1、創(chuàng)建Facade
一般地,實際的應用程序都有一個 Facade 子類,這個 Facade 類對象負責初始化 Proxy,Mediator和Command,建立 Command 與 Notification 名之間的映射,或是通過執(zhí)行一個 Command 來注冊所有的 Proxy和 Mediator。這里命名一個“AppFacade”的類并做成一個個人熟悉的單例:

我們在初始化controller時注冊一個”啟動游戲“的Notification和command,并在Appfacade中提供一個啟動mvc的對外接口Launch
(Notification和Command的映射放在Appfacade初始化時注冊,Controller 會注冊偵聽每一個 Notification,當被通知到時,Controller 會實例化一個該 Notification 對應的 Command 類的對象。最后,將 Notification 作為參數(shù)傳遞給execute 方法。具體可以參考Command基類的實現(xiàn)。也就是說,Command的執(zhí)行是通過發(fā)送Notification通知操作的。Command 對象是無狀態(tài)的;只有在需要的時候( Controller 收到相應的Notification)才會被創(chuàng)建,并且在被執(zhí)行(調用 execute 方法)之后就會被刪除。所以不要在那些生命周期長的對象(long-living object)里引用 Command 對象)
(Command 要實現(xiàn) ICommand 接口。在 PureMVC 中有兩個類實現(xiàn)了ICommand 接口:SimpleCommand、MacroCommand。SimpleCommand 只有一個 execute 方法,execute 方法接受一個Inotification 實例做為參數(shù)。實際應用中,你只需要重寫這個方法就行了)
這里有一個方法僅作參考:當?Notification 的名稱常量需要被其他的程序訪問時,我們可以使用單獨的“ApplicationConstants”類或者文件“enum”等來存放這些 Notification 名稱常量定義。不管什么時候,都應該把 Notification(通知)名稱定義為常量,需要引用一個 Notification 時就使用它的名稱常量,這樣做可以避免一些編譯時無法發(fā)現(xiàn)的錯誤。因為編譯器可以檢查常量;而使用字符串,如果你手誤輸入錯誤的字符串,編譯器也不法知道,也無從報錯。



簡單驗證一下command或者說mvc等有效性,我們在編輯器中創(chuàng)建一個根節(jié)點(2D,備注:demo針對2D項目,編輯器版本3.3.2,后面不再重復),并綁定一個叫“GameApp“的腳本。
在StartUpCommand中打印一下日志:



運行一下:

還可以。先到這兒,太??了