參考
PureMVC(AS3)剖析
我的FLASH情結2010 - 淺談網(wǎng)頁游戲與創(chuàng)業(yè)
菜鳥學PureMVC記
PureMVC(JS版)源碼解析:總結
Flash務實主義(四)——Flash中的MVC
一槐瑞、痛點
參考
android mvc mvp mvvm
PureMVC--一款多平臺MVC框架
1.傳統(tǒng)MVC的痛點
Controller:控制器邑滨,包含了項目的業(yè)務邏輯诊霹。但是也是被大家吐槽最多的一個奇瘦,原因就是很多人瑟捣,或者說大多數(shù)人陕悬,習慣于什么都往Controller里寫习柠,最后一個Controller超過1000行代碼是司空見慣的事疏魏。所以關于傳統(tǒng)MVC的第一個痛點就是蛉艾,Controller過于臃腫钳踊。
Model:模型衷敌,包含了項目的數(shù)據(jù)模型。MVC定義之初拓瞪,Model是核心缴罗,旨在使得同一個Model可以被復用到多個項目或者被復用到同一個項目的不同模塊之中。但是在實際項目中祭埂,Model還承載著純Model層內部的運算的工作面氓,但是運算部分會項目的不同而有所區(qū)別,因此與項目的適配反而成為了Model可復用的枷鎖蛆橡。所以關于傳統(tǒng)MVC的第二個痛點就是舌界,Model變得不可復用。
View:視圖泰演,包含了項目所有的UI組件呻拌。視圖本身沒有什么好被大家詬病的,但是由于MVC中對于View和Controller界限的模糊界定造成了使用者在寫代碼的時候會覺得這部分代碼放在View或者Controller里都可以的情況睦焕。例如事件的處理藐握,組件的組合等。所以關于傳統(tǒng)MVC的第三個痛點就是垃喊,View概念的模糊猾普。
2.PureMVC到底如何解決了傳統(tǒng)MVC的三個痛點?
Controller將操作邏輯細化為Command
根據(jù)PureMVC的最佳實踐本谜,Controller實體不需要單獨實現(xiàn)初家,且Controller內部將每一個操作分割為一個個Command,這從根本上解決了Controller越來越臃腫的問題耕突,強制用戶將Controller里每一個操作細粒度化笤成,使得代碼可讀性更強,維護性更高眷茁。Proxy負責域邏輯炕泳,DataObject負責數(shù)據(jù)模型
PureMVC中,與域相關的邏輯和接口由Proxy來負責上祈,后續(xù)的添加和修改接口只在Proxy中完成培遵。而DataObject是完全對業(yè)務進行數(shù)據(jù)建模而產(chǎn)生的數(shù)據(jù)模型,與業(yè)務沒有絲毫的關系登刺,因此也保證了高可移植性籽腕。ViewComponent只關注UI,其余的交給Mediator
PureMVC規(guī)定了ViewComponent只負責UI的繪制纸俭,而其他事情皇耗,包括事件的綁定統(tǒng)統(tǒng)交給Mediator來做。這也就避免了ViewComponent內部代碼定義模糊揍很,更不會和Controller的代碼進行混淆郎楼。
二万伤、使用細節(jié)
1.Command需要偵聽通知,使用registerCommand注冊事件偵聽
根據(jù)《pureMVC最佳實踐》中的建議呜袁,我的做法是這樣的敌买,盡量使用Command,讓Command成為Mediator與Proxy之間通訊的唯一橋梁阶界,Mediator和Proxy中發(fā)出的Notification虹钮,接收者一定是某個Command,然后再由Command處理并將結果轉發(fā)給真正的消息接收者膘融,Command就算僅僅起一個轉發(fā)作用芙粱,僅僅有不到10行代碼,也要創(chuàng)建一個Command類托启。這樣不僅使你的架構更加清晰宅倒,而且也更符合MVC思想,Command類的大量存在還使你架構的業(yè)務邏輯具有了更好的封裝性和擴展性屯耸,可謂是一箭三雕拐迁,何樂而不為?唯一的負面影響可能是你需要創(chuàng)建和維護更多的Command類文件疗绣,但相對于優(yōu)勢而言线召,這點影響不算啥。
參考PureMVC(JS版)源碼解析(六):MacroCommand類
在實際開發(fā)過程中多矮,我們需要寫一些復雜的邏輯處理單元(Command類)缓淹,這寫邏輯處理類要么繼承SimpleCommand,要么繼承MacroCommand類塔逃,哪什么時候繼承MacroCommand類讯壶,什么時候繼承SimpleCommand類,需要看我們邏輯的復雜度湾盗,如果一個邏輯單元可以拆分為多個子邏輯單元伏蚊,那我們可以繼承MacroCommand類,如果一個邏輯單元就可以處理格粪,那我們只需要繼承SimpleCommand類躏吊。繼承MacroCommand類的子類需要重寫initializeMacroCommand方法,不需要重寫execute方法帐萎,繼承SimpleCommand類的子類需要重寫execute方法比伏。
參考PureMVC(JS版)源碼解析(十):Controller類
executeCommand()方法告訴我們, Command對象(SimpleCommand或者MacroCommand)是無狀態(tài)的疆导;只有在需要的時候(Controller收到相應的Notification)才會被創(chuàng)建赁项,并且被執(zhí)行(調用execute方法)。
2.xxxCanvasMediator需要偵聽通知,需要在對應Mediator中使用listNotificationInterests注冊肤舞,并重寫handleNotification處理紫新。
3.Proxy 集中程序的Domain Logic(域邏輯),并對外公布操作數(shù)據(jù)對象的API李剖。它封裝了所有對數(shù)據(jù)模型的操作,不管數(shù)據(jù)是客戶端還是服務器端的囤耳。
模型部分在實際開發(fā)中除了存儲數(shù)據(jù)篙顺,還有其他作用么?是的充择,其實它的實際職責非常多德玫。它要給Command和Mediator提供接口,響應用戶操作椎麦,進行數(shù)據(jù)操作或者請求遠程數(shù)據(jù)服務宰僧,進行數(shù)據(jù)的序列化和反序列化,得到異步數(shù)據(jù)后可能還要檢查數(shù)據(jù)合法化观挎。但不管怎么樣琴儿,它始終是在和數(shù)據(jù)打交道,同時也應該是你的主程序中唯一可以直接和數(shù)據(jù)打交道的管道嘁捷,別的部分要想和數(shù)據(jù)有接觸造成,首先要問問它同意不同意。模型處理完數(shù)據(jù)會以Notification的消息方式通知Command或者Mediator雄嚣。但絕對不能在Proxy中直接調用Mediator晒屎,這是為了保證數(shù)據(jù)層的獨立性、可移植性和重用性缓升,也簡化了你的架構思想鼓鲁。
參考PureMVC(JS版)源碼解析(八):Proxy類
另外,和Mediator類一樣港谊,Proxy類也要在facade中注冊骇吭,它也有onRegister()/onRemove()方法,這里就不在陳述封锉,具體用法和Mediator類的onRegister()/onRemove()用法一樣绵跷。
總結一下:Proxy類的結構很簡單,我們只需要記住兩點成福,一是它有一個data屬性碾局,可以用來存儲它所管理的data model,二是它可以發(fā)送消息,不能接受消息奴艾。
4.應該避免Mediator與Proxy 直接交互净当。例子項目中遵從了這個規(guī)則,但實際上項目Mediator中不可避免需要獲取Proxy數(shù)據(jù),如果每次都通過一個Notification去獲取數(shù)據(jù)像啼,然后返回數(shù)據(jù)給Mediator俘闯,這樣無形中增加了通信次數(shù)、帶反饋數(shù)據(jù)的通信加重通信負擔忽冻。所以可以適當是的在Mediator中facade.retrieveProxy獲取Proxy然后拿到數(shù)據(jù)真朗,而且從proxy直接拿數(shù)據(jù),可以保證拿到最新數(shù)據(jù)僧诚。
5.永遠不要把 Event 的名稱定義在 Fa?ade 類里遮婶。應該把 Event 名稱常量定
義在那些發(fā)送事件的地方,或者就定義在 Event 類里湖笨。
6.參考Pure MVC使用的幾點心得(模式和使用誤區(qū))
注冊mediator/command/proxy的時機不合適和沒有及時注銷旗扑。當初所有command/proxy的注冊我都是放在應用初始化的時候做,就是無論用戶是否真的用到這些功能慈省,都已經(jīng)注冊(占用客戶端的內存)了臀防。另外比較嚴重的一點是,根本沒有注銷边败!那樣會怎樣袱衷,客戶端的內存會一直占用,直到退出瀏覽器放闺,后果可想而知祟昭。正確的做法當然是有需求才去滿足需求啦。當用戶點擊某個button怖侦,menu篡悟,下拉菜單時,才去注冊mediator/command/proxy匾寝,用完之后及時注銷搬葬。當然,如果有些功能模塊是經(jīng)常用到的艳悔,可以常駐內存急凰,這樣會提高一點效率。
7.參考Flash務實主義(四)——Flash中的MVC
是Mediator知道View的一切猜年,View完全不知道Mediator抡锈,而不是相反
對于使用pureMVC的同僚們,我真是不明白你們到底是怎么把這個反過來理解成“View知道Mediator的一切乔外,而Mediator完全不知道View”的床三,因為官方實例上寫的很明白。估計是把mediator當成通信專用的模塊類了吧杨幼。但是如果你放棄了mediator分離View代碼的特性撇簿,只是用來通信的話聂渊,至少要保留原來的通信功能,就是讓Mediator依然可以直接訪問View四瘫。否則既然Mediator是用來通信的汉嗽,它卻不能操作View,結果還得設法和View再通信一次……
8.參考PureMVC的十個小提示 原作者杜增強
在某些情況下你不再使用一個Mediator和它的View Components. 你應該用facade.removeMediator(MyMediator.NAME)去掉這個Mediator同時用 destroy()來去掉包含所有l(wèi)isteners,timer,references的ViewComponent.以便更好的進行 垃圾回收.
Q:Mediator可以handleNotification找蜜,Command也可以和notification關聯(lián)饼暑,到底怎么來決定用誰處理呢?
A:還是回到純·摸魚吃锹杈,什么時候要軍隊自己去接通知撵孤,什么時候需要將軍來接通知執(zhí)行呢?通常情況下竭望,“內部的小事”就由軍隊自己處理,比如騎兵一隊的馬餓了裕菠,發(fā)出一個通知“勞資餓了”咬清,那么騎兵一隊自己接這個通知然后去喂馬就好了,這樣做的好處就是保證了主要邏輯的簡潔奴潘,不用勞煩將軍再去操心這些小事旧烧。那么“關系到集體的大事”就得由將軍去處理了,比如通知需要哪幾只部隊連夜突襲画髓,發(fā)一個通知讓將軍來調度更加安全保險掘剪,這樣做另外的好處就是重用性好 ,否則每個軍隊都得去接這個通知奈虾,然后看看是不是通知了自己夺谁,萬一有些軍隊忘了接這個通知,打仗輸了可就慘咯肉微!(簡單的邏輯交給Mediator處理)
Q:在registerMediator的時候匾鸥,viewComponent怎么寫?
A:寫它要管理的視圖對象
Q:我怎么看見有人寫的 viewComponent是main碉纳,而不是管理的視圖對象勿负?
A:說明它要在內部創(chuàng)建視圖對象,然后添加到main上面劳曹,所以傳入了main奴愉。這樣做就完全把那個視圖對象 給保護了,還是那句話铁孵,這是很靈活的锭硼。
心得:
1.一個Command也可以關心多個通知,然后用一個Switch來判斷處理库菲。也可以給通知加上Type屬性账忘,在處理時進行分別處理。
2.多思考結構,積極重構鳖擒,這會讓你的程序后期維護和改進非常容易
3.建立一個AppMediator來管理整個main視圖溉浙,這樣就能提供他的viewComponent來獲取main
4.建一個PublicNotification類來提供常用的通知名稱
5.用onRemove()函數(shù),及時清除對象釋放內存
6.靈活處理蒋荚,教科書只是參考戳稽,按你自己的方法也許更快更方便。
7.不要為了MVC而MVC期升,如果一個東西要徹底解耦是不太現(xiàn)實的惊奇,一些不會擴展的小東西完全可以怎么快怎么來。
10.參考總結PureMVC中Mediator,Command,Proxy的職責
11.參考PureMVC(AS3)剖析:吐槽
為了模塊間解耦播赁,Notification發(fā)布者應該不關心誰對這個消息感興趣(誰來處理)颂郎,感興趣者自行注冊(Mediator通過listNotificationInterests注冊、Command通過facade.registerCommand()注冊)容为。
無法知道Notification的源頭乓序。然而這點可以通過在消息體body中,增加字段標識坎背,如:
sendNotification(ApplicationConstants.UPDATE_LEVEL_DATA,
{ "noticeSource": this, "levelData": m_levelData } );
noticeSource標識消息來源替劈,如果您還想要知道消息傳遞層次,可以用數(shù)組表示得滤,順序插入傳遞者陨献。
三、Egret中使用PureMVC
參考
使用egret開發(fā)2048
關于使用pureMvc開發(fā)2048懂更,出現(xiàn) Cannot find name 'puremvc'.問題
如何在egret中使用pureMVC
我們只需要他提供的一個js文件和一個.d.ts文件眨业。 然后復制到對應位置。改一下index.html和release.html如果要打包成native還要改一下native_loader.js膜蛔,具體怎么改你看GitHub上對應的文件 坛猪。 這里還有一點沒有說清楚的是 pureMVC的作者給的那個js不要用,他多了一點define的東西皂股,我把他去掉了就可以在ts項目里面用了墅茉。
引用了第三方類庫之后要先 build -e 一次的。
四呜呐、Laya 中使用 PureMVC
1.同Egret,把define那部分注釋掉就斤。
2.注意,如果下載了multicore版本蘑辑,寫法有一點點區(qū)別洋机。大多數(shù)語言都支持兩個版本的框架,一個是標準版洋魂,不支持多模塊绷旗,另一個是multicore版喜鼓,支持多模塊。參考pureMVC衔肢,多核和單核的區(qū)別,多核可以有多個Facade的實例庄岖,單核只有一個
class AppFacade extends puremvc.Facade {
constructor() {
super();
}
public static readonly STARTUP: string = "startup";
public static readonly MOUSE_CLICK: string = "MOUSE_CLICK";
public static getInstance(): AppFacade {
if (this.instance == null) this.instance = new AppFacade();
return <AppFacade>(this.instance);
}
public initializeController(): void {
super.initializeController();
this.registerCommand(AppFacade.STARTUP, StartupCommand);
this.registerCommand(AppFacade.MOUSE_CLICK, MouseClickCommand);
}
/**
* 啟動PureMVC,在應用程序中調用此方法角骤,并傳遞應用程序本身的引用
* @param rootView - PureMVC應用程序的根視圖root隅忿,包含其它所有的View Componet
*/
public startUp(rootView: any): void {
this.sendNotification(AppFacade.STARTUP, rootView);
this.removeCommand(AppFacade.STARTUP); //PureMVC初始化完成,注銷STARUP命令
}
}
class StartupCommand extends puremvc.SimpleCommand {
public constructor() {
super();
}
public execute(notification: puremvc.INotification): void {
this.facade.registerProxy(new MouseClickProxy());
var stage: Laya.Stage = notification.getBody() as Laya.Stage;
this.facade.registerMediator(new StageMediator(stage));
}
}
class MouseClickProxy extends puremvc.Proxy implements puremvc.IProxy {
public static NAME: string = "MouseClickProxy";
public constructor() {
super(MouseClickProxy.NAME);
}
}
class StageMediator extends puremvc.Mediator implements puremvc.IMediator{
public static NAME:string = "StageMediator";
public constructor(viewComponent:Laya.Stage){
super(StageMediator.NAME, viewComponent);
//注冊監(jiān)聽邦尊,從而獲取UI上的事件
viewComponent.on(Laya.Event.CLICK, this,this.mouseClick);
}
private mouseClick(event:Laya.Event):void
{
this.sendNotification(AppFacade.MOUSE_CLICK, event.stageX);
}
}
class MouseClickCommand extends puremvc.SimpleCommand {
public constructor() {
super();
}
public execute(notification: puremvc.INotification): void {
var stageX: number = <number>notification.getBody();
console.log("click pos:",stageX);
}
}
// 程序入口
class GameMain{
constructor()
{
Laya.init(600,400);
AppFacade.getInstance().startUp(Laya.stage);
}
}
new GameMain();
multicore版本部分代碼
public static BG_NAME:string = "BGH5Video";
public static readonly STARTUP: string = "startup";
public static readonly MOUSE_CLICK: string = "MOUSE_CLICK";
// public static getInstance(): AppFacade {
// if (this.instance == null) this.instance = new AppFacade();
// return <AppFacade>(this.instance);
// }
public static getInstance():AppFacade {
if( !AppFacade.instanceMap[ AppFacade.BG_NAME ] )
AppFacade.instanceMap[ AppFacade.BG_NAME ] = new AppFacade( );
return AppFacade.instanceMap[ AppFacade.BG_NAME ];
}
class StartupCommand extends puremvc.SimpleCommand {
public constructor() {
super();
}
public execute(notification: puremvc.INotification): void {
this.facade().registerProxy(new MouseClickProxy());
var stage: Laya.Stage = notification.getBody() as Laya.Stage;
this.facade().registerMediator(new StageMediator(stage));
}
}
五背桐、puremvc 場景切換
使用 PureMVC 構建游戲項目 Part IV 之場景管理
注意,如果關閉某個窗口蝉揍,只是把它從舞臺移除链峭,但是沒有removeMediator時。此mediator通過listNotificationInterests偵聽的事件仍然會執(zhí)行
end