一.简介

  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学习笔记的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  4. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  7. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  8. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

  9. DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

    今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...

随机推荐

  1. VSCode 在 Vue 导入路径中使用 @ 符号后无法正确跳转 bug

    VSCode 在 Vue 导入路径中使用 @ 符号后无法正确跳转 bug bug jsconfig.json { // This file is required for VSCode to unde ...

  2. npm config set registry

    npm config set registry $ npm config set registry $ npm config set registry https://registry.your-re ...

  3. github & gist & Weekly development breakdown

    github & gist & Weekly development breakdown https://gist.github.com/xgqfrms WakaTime waka-b ...

  4. axios upload excel file

    axios upload excel file https://github.com/axios/axios/issues/1660 https://stackoverflow.com/questio ...

  5. vuex bug & vue computed setter

    vuex bug & vue computed setter https://vuejs.org/v2/guide/computed.html#Computed-Setter [Vue war ...

  6. NGK” 呼叫河马 “智能合约火爆全网

    最近有一款基于NGK.IO公链上的智能合约"呼叫河马"在区块链市场很火.通过访问和查阅资料可知,"呼叫河马"是一款全新的智能合约Dapp小游戏,智能合约代码是1 ...

  7. python的with用法(转载)

    原文地址:https://www.cnblogs.com/wanglei-xiaoshitou1/p/9238275.html 一.with语句是什么? 有一些任务,可能事先需要设置,事后做清理工作. ...

  8. Java volatile 关键字底层实现原理解析

    本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...

  9. Java SE7虚拟机指令操作码助记符

    本文转载自Java SE7 虚拟机指令操作码助记符 导语 在Class文件中,Java方法里的方法体,也就是代表着一个Java源码程序中程序的部分存储在方法表集合的Code属性中.存储在Code属性中 ...

  10. linux 安装node和pm2

    用yum安装 curl -sL https://rpm.nodesource.com/setup_10.x | bash - yum install -y nodejs npm install -g ...