PureMVC學習筆記

movin2333發表於2021-03-26

一.簡介

  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。