• PureMVC学习笔记


    一.简介

      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。

  • 相关阅读:
    用Groovy处理JMeter变量
    用Groovy处理JMeter断言和日志
    选择手动测试还是自动化测试?
    从单元测试标准中学习
    利用ThreadLocal解决线程同步问题
    JSON基础
    Java中interface属性和实例方法
    集成测试、单元测试、系统测试
    异步查询转同步加redis业务实现的BUG分享
    《深入理解java虚拟机》读书笔记三——第四章
  • 原文地址:https://www.cnblogs.com/movin2333/p/14584491.html
Copyright © 2020-2023  润新知