一.簡介
PureMVC是基於MVC思想和一些基礎設計模式建立的一個輕量級的應用框架,免費開源,最初是執行的ActionScript 3語言使用,現在已經移植到幾乎所有主流平臺。PureMVC官方網站:http://puremvc.org,框架及其響應的說明文件直接在官網中下載即可。
二.基本結構
PureMVC使用了代理模式、中介者模式、外觀模式、命令模式、觀察者模式、單例模式等包裝MVC,使MVC更加框架化。
Model(資料模型)使用Proxy代理物件負責處理資料,View(介面)關聯Mediator中介物件負責處理介面,Controller(業務控制)管理Command命令物件,負責處理業務邏輯,Facade(外觀)使MVC三者的經紀人,統管全域性,Notification(通知)負責傳遞資訊。
三.PureMVC的基礎使用
1.將PureMVC拷貝到Unity專案中
下載C#版本的PureMVC,是一個壓縮包,有兩種方式匯入
1)匯入dll包,使用vs開啟其中的PureMVC.sln檔案,就可以開啟整個工程,然後使用vs生成dll包,在bin/debug/netcoreapp3.0資料夾下的dll檔案賦值到Unity中Assets目錄下的Plugins資料夾下。這種方法安全性相對較高,使用時推薦使用這種方式匯入。
2)匯入C#原始碼
將PureMVC資料夾下的Core、Interfaces、Patterns三個資料夾複製到Unity專案中即可。學習階段推薦使用原始碼匯入,能夠看到程式碼實現。
2.建立通知名類
在使用PureMVC時,使用字串傳遞通知訊息,為了方便呼叫防止寫錯,可以宣告一個通知名類用於管理所有通知名。
/// <summary> /// 儲存所有通知名稱,方便管理呼叫,防止寫錯 /// </summary> public class PureNotificationNames { public const string SHOW_PANEL = "showPanel"; }
3.Model和Proxy
1)建立資料物件Model,在Model中只儲存資料的型別,不對資料作任何處理。
/// <summary> /// 資料物件,只需要宣告資料物件持有的變數 /// </summary> public class PlayerDataObj { private string playerName; public string PlayerName { get { return playerName; } set { playerName = value; } } private int lev; public int Lev { get { return lev; } set { lev = value; } } private int money; public int Money { get { return money; } set { money = value; } } private int power; public int Power { get { return power; } set { power = value; } } }
2)宣告資料的代理Proxy,在代理中處理資料。
/// <summary> /// 玩家資料代理物件,處理資料更新邏輯 /// </summary> public class PlayerProxy : Proxy { //代理名稱,父類中的預設名稱為Proxy,使用new關鍵字隱藏父類的名稱 public new const string NAME = "PlayerProxy"; /// <summary> /// 必須寫建構函式,在建構函式中必須呼叫父類的建構函式,Proxy中只提供了一個有參構造 /// 可以在建構函式中從外部傳入資料data使用,也可以在建構函式中初始化資料 /// </summary> public PlayerProxy() : base(NAME) { //建構函式中初始化資料 PlayerDataObj data = new PlayerDataObj(); //初始化 data.PlayerName = PlayerPrefs.GetString("playerName"); data.Money = PlayerPrefs.GetInt("money"); //關聯 Data = data; } //從外部傳入資料 public PlayerProxy(PlayerDataObj data) : base(NAME, data) { } //提供對資料操作的其他方法 public void UpdateLev() { } public void SaveData() { } }
4.View和Mediator
1)建立檢視類View,和model類似,view只負責持有皮膚中地相關控制元件即可,控制元件的資訊顯示、方法註冊等由mediator負責。
/// <summary> /// View負責持有當前View下的所有控制元件,可以提供更新皮膚的方法 /// </summary> public class MainView : MonoBehaviour { public Button btnRole; public Button btnSill; public Text txtName; public Text txtMoney; public Text txtPower; public void UpdateInfo(PlayerDataObj data) { txtName.text = data.PlayerName; txtMoney.text = data.Money.ToString(); txtPower.text = data.Power.ToString(); } }
2)建立中介Mediator,負責view中的控制元件的顯示、更新等。
public class MainViewMediator : Mediator { public new const string NAME = "MainViewMediator"; /// <summary> /// 和proxy的構造方法類似 /// 需要初始化持有的皮膚panel,可以外部傳入也可以內部生成 /// </summary> public MainViewMediator() : base(NAME) { } /// <summary> /// 重寫監聽通知的方法,類似於註冊事件 /// 關心哪些通知就返回響應的通知名稱即可 /// </summary> /// <returns></returns> public override string[] ListNotificationInterests() { return new string[] { PureNotificationNames.UPDATE_PLAYER_INFO, PureNotificationNames.SHOW_PANEL }; } /// <summary> /// 重寫處理通知的方法 /// </summary> /// <param name="notification">介面物件中包含Name(通知名)和Body(通知包含的資訊)兩個重要引數</param> public override void HandleNotification(INotification notification) { //根據通知的名稱作相應的處理 switch (notification.Name) { default: break; } } /// <summary> /// 可選:重寫註冊時的方法 /// </summary> public override void OnRegister() { base.OnRegister(); } }
5.Facade、Controller和Command
1)Facade是所有Command、Mediator和Proxy的管理類。在InitializeController函式中使用RegisterCommand方法註冊Command,類似於委託的註冊方式,第一個引數為命令名稱,第二個引數是一個無參函式,其返回值為繫結的Command命令。使用SendNotification方法啟動命令(可以外部通過facade物件呼叫,也可以提供給外部啟動命令的方法作對這個方法進一步封裝)。
public class GameFacade : Facade { //facade已經是單例(下載時決定的),可以提供靜態公共屬性Instance,方便使用,父類中已經提供靜態私有的instance變數 public static GameFacade Instance { get { if(instance == null) { instance = new GameFacade(); } return instance as GameFacade; } } /// <summary> /// 初始化controller相關內容 /// </summary> protected override void InitializeController() { //可以保留,父類中初始化時new了一個controller base.InitializeController(); //命令和通知繫結的邏輯 //註冊通知,類似於委託,在函式中返回一個命令, RegisterCommand(PureNotificationNames.START_UP, () => { return new StartupCommand(); }); } /// <summary> /// 啟動命令的函式,其他函式呼叫這個函式啟動命令 /// </summary> public void StartUp() { SendNotification(PureNotificationNames.START_UP); } }
2)Command命令,在Command中重寫Execute方法,書寫命令執行邏輯。
public class StartupCommand : SimpleCommand { /// <summary> /// 重寫execute方法,當命令被執行時呼叫 /// </summary> /// <param name="notification"></param> public override void Execute(INotification notification) { base.Execute(notification); Debug.Log("123123"); } }
四.PureMVC的基本使用的呼叫流程梳理
1.書寫自己的Facade類,繼承Facade類,提供這個類的單例模式屬性Instance(父類Facade中已經有單例物件instance了,並且提供了GetInstance方法獲取instance,但是這個方法的返回值是Facade類實現的介面IFacade,獲取時還需要傳入例項化instance的方法,使用不方便),方便呼叫。
2.使用自己的Facade類物件的SendNotification方法傳送通知,可以對這個方法進行封裝,引數有三個,一個必選引數通知名,兩個可選引數通知傳遞的引數和通知型別。現在已經傳送了通知,這個方法層層呼叫了View和Observer中的一些方法,本質上還是對委託的封裝,如果有興趣可以自行探索,下面是找到的一些這個方法的呼叫鏈的程式碼:
上面五張圖的程式碼分別來自於Facade類、Facade類、View類、Observer類、Observer類,可以看到執行的順序是首先呼叫view物件的NotifyObservers方法,通知view,view會呼叫observer物件執行通知。
3.一定存在一個註冊通知的函式,否則自己定義的通知無法執行。在自己定義的Facade函式中重寫InitializeController方法,在這個方法中呼叫RegisterCommand函式註冊通知。
被重寫的父類Facade中的InitializeController函式中例項了Controller,這個函式被InitiateFacade函式呼叫,而InitiateFacade函式又被Facade類的建構函式呼叫,因此在Facade及其子類被構造時會執行InitializeController方法。
RegisterCommand方法由Facade父類提供,這個方法呼叫了controller物件的RegisterCommand方法,controller物件的RegisterCommand方法首先校驗是否View中是否有這個通知,如果沒有需要將通知儲存到View中,然後將方法儲存到一個controller物件的ConcurrentDictionary型別只讀變數中。也就是說最終這個通知會同時註冊到View和Controller中。view中會將通知註冊到觀察者Observer中,呼叫時通過view通知observer呼叫controller中的通知方法。
我們仔細觀察字典會發現字典的值是一個Func型別的委託,泛型為ICommand,也就是說這個委託有一個ICommand型別的返回值,這個返回的Command值就是我們的通知對應的邏輯程式碼所在的類,實際上在自定義的Facade類中InitializeController函式中使用RegisterCommand方法註冊通知時引數中的拉姆達表示式必須要有一個ICommand型別的返回值。
4.接下來我們就需要定義剛才註冊通知時返回的Command類。自定義的Command類繼承自SimpleCommand類或者MacroCommand類(都實現了ICommand介面)。SimpleCommand必須重寫Execute方法,當前Command需要執行的邏輯程式碼就定義在這個方法中;MacroCommand必須重寫InitializeMacroCommand方法,它持有一個IList<Func<ICommand>>型別的subCommands變數,MacroCommand可以持有多個SimpleCommand或者MacroCommand,都儲存在subCommands變數中,它的Execute方法已經定義好了不用重寫,Execute函式會依次執行其持有的所有SimpleCommand和MacroCommand,在InitializeMacroCommand方法中通過AddSubCommand方法將Command加入subCommands變數即可。下面是這兩種Command的方法和屬性截圖:
SimpleCommand中的Execute方法,需要重寫。
MacroCommand的構造方法,呼叫了InitializeMacroCommand方法。
MacroCommand的InitializeMacroCommand方法,需要重寫。
MacroCommand的AddSubCommand方法,在InitializeMacroCommand方法中通過這個方法為MacroCommand新增Command,和在自定義facade中註冊Command時類似,引數是一個ICommand返回值的無參Func委託,將需要新增的Command作為返回值返回。
MacroCommand的Execute函式,這個函式按照新增順序依次執行其中的Command。
MacroCommand中儲存所有持有的Command引用的subCommands只讀變數。
5.INotification和Notification:Notification通知類是INotification的實現類,這個類中有三個屬性(見下圖):
其中Name只讀屬性是這個通知的名稱,Body屬性是這個通知帶著的資料物件,Type屬性是這個資料的型別。Notification通知類是框架各部分之間交流的資料載體,也就是基本結構圖中的箭頭。
6.回看本文第一張圖,也就是基本結構圖。途中Facade發出通知(Notification),箭頭分別指向了Controller、View和Model,我們在3中也通過呼叫鏈得知通知被同時註冊到了controller和view中,因此釋出通知時,controller和view同時都會接收通知,然後controller通過通知找到相應的command執行execute函式,view同時也會通過通知找到相應的mediator執行函式。接下來自定義mediator。自定義的Mediator繼承了Mediator類,需要實現構造方法,呼叫父類的構造方法(Mediator類只提供了有參構造,如下圖:)
Mediator的名稱用於將Mediator註冊到facade中使用。接下來重寫ListNotificationInterests方法,這個方法的返回值是一個字串陣列,將這個Mediator需要監聽的所有通知名稱返回。然後重寫HandleNotification方法,在這個方法中根據剛才監聽的通知名稱執行相應的邏輯,如下圖所示:
下面是這兩個方法的呼叫鏈:
在Facade類中通過RegisterMediator註冊mediator時,會呼叫view的RegisterMediator方法。
在view物件中的RegisterMediator會嘗試將mediator物件加入到其持有的mediaMap變數中,這是一個ConcurrentDictionary型別的變數,用於儲存所有註冊到Facade中的Mediator,如果成功將mediator物件加入到了mediaMap這個字典中,說明這個mediator沒有註冊,接下來通過ListNotificationInterests獲得mediator監聽的通知,然後生成observer觀察者物件,將通知名稱和observer物件逐個通過RegisterObserver方法註冊。註冊完成後呼叫OnRegister方法,這個方法在自定義的Mediator中可以根據需要選擇是否重寫。
7.框架部分的呼叫鏈基本梳理完成。在Unity中使用PureMVC框架還有3個型別的類是必須有的:
1)view皮膚元件,持有皮膚的各種控制元件,提供一些更新顯示等方法供外界呼叫,屬於MVC的V。注意:view皮膚元件繼承MonoBehaviour類,是掛載在皮膚上的指令碼;PureMVC中也有一個View類,這個類繼承自IView介面,使用框架時不會涉及到View類;這裡的view皮膚元件和View類並不相同。
2)資料Model類,遊戲中的資料模型,不用宣告繼承任何類或實現介面,只需要提供遊戲中的資料物件的屬性即可,任何方法不寫都可以,供資訊傳遞使用,屬於MVC的M。
3)自定義Proxy代理類,繼承Proxy類,在使用時需要首先在Facade中註冊(建構函式的寫法和Mediator幾乎相同,因為都需要註冊到Facade中使用)。這個類用於提供處理資料模型Model的各種方法,屬於MVC的M。