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。
PureMVC学习笔记的更多相关文章
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
- 2014年暑假c#学习笔记目录
2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
- CSS学习笔记
CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...
- HTML学习笔记
HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...
- DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记
今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...
随机推荐
- value-key
value-key object 如果 Select 的绑定值为对象类型,请务必指定 value-key 作为它的唯一性标识. value-key 作为 value 唯一标识的键名,绑定值为对象类型时 ...
- JavaScript 实现 (ECMAScript 6)
JavaScript 的核心 ECMAScript 描述了该语言的语法和基本对象: DOM 描述了处理网页内容的方法和接口: BOM 描述了与浏览器进行交互的方法和接口. ECMAScript.DOM ...
- how to enable vue cli auto open the localhost url
how to enable vue cli auto open the localhost URL bad you must click the link by manually, waste of ...
- Android Activity All In One
Android Activity All In One Android Activity Lifecycle https://developer.android.com/reference/andro ...
- WebIDE All In One
WebIDE All In One web IDE Visual Studio Code vscode Code editing Redefined. Free. Built on open sour ...
- website SEO all in one
website SEO all in one SEO 指标量化 https://www.similarweb.com/zh/top-websites/ demo https://www.similar ...
- holy shit StackOverflow
holy shit StackOverflow refs https://stackoverflow.com/users/5934465/xgqfrms?tab=questions xgqfrms 2 ...
- IM SDK & websocket & chart room
IM SDK & websocket & chart room IM SDK https://imsdk.com/ https://cloud.tencent.com/document ...
- flatbuffer与protobuf对比
在内存空间占用这个指标上,FlatBuffers占用的内存空间比protobuf多了两倍.序列化时二者的cpu计算时间FB比PB快了3000ms左右,反序列化时二者的cpu计算时间FB比PB快了900 ...
- Linux系统管理--part(1)
Linux系统管理--part(1) Linux系统安装完毕,需要对Linux系统进行管理和维护,让Linux服务器能够真正英语于企业中 Linux运维的三个步骤安装.调试.启动 通过本篇文章,将学习 ...