PureMVC框架讲解
1. 理论基础
Pure MVC是在基于模型、视图和控制器MVC模式建立的一个轻量级的应用框架,这个框架是开源免费的,目前支持18中开发语言。
- 应用PureMVC主要目的就是“解耦”,除了层之间的解耦外,我们也可 以在层内,进行进一步的划分。
- 模型层一般都分为:“数据实体类(值对象)”与“数据代理类” 好处是,“数据本身”与“数据操作”相分离,易于存储、扩展等,耦合性降低与扩展性提高。
- 视图层也可以基于降低“层内”的耦合性的思想分为:“显示View” 与“显示控制 Mediator”,View : 只负责简单的窗体数据显示;Mediator:负责显示层的通讯、按钮点击事件、数据处理。
官网:http://puremvc.org
github地址:https://github.com/PureMVC
1.1 PureMVC整体架构
- Model 与 Proxy
Model保存对Proxy对象的引用,Proxy负责操作数据模型,存取数据。这样保证了Model层的可移植性。 - View 与 Mediator
View保存对Mediator对象的引用。由Mediator对象来操作具体的视图组件(View Component),包括:添加事件监听器,发送或接收Notification ,直接改变视图组件的状态。这样做实现了把视图和控制它的逻辑分离开来。 - Controller 与 Command
Controller保存所有Command的映射。Command类是无状态的,只在需要时才被创建。
Command可以获取Proxy对象并与之交互,发送Notification,执行其他的Command。经常用于复杂的或系统范围的操作,如应用程序的“启动”和“关闭”。应用程序的业务逻辑应该在这里实现。 - Facade 与 Model,View和Controller
Facade类应用单例模式,它负责初始化核心层(Model,View和Controller),并能访问它们的Public方法。在实际的应用中,只需继承Facade类创建一个具体的Facade类就可以实现整个MVC模式,并不需要在代码中导入编写Model,View和Controller类。
Proxy、Mediator和Command就可以通过创建的Facade类来相互访问通信。
1.2 设计模式
单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
迪米特法则(Law of Demeter,简称LoD)也叫最小知识原则(Least Knowledge Principle 简写LKP)如果俩个类不必直接通信,那么这俩类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
命令模式(Command):讲一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
外观模式(Facade):为了系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
观察者模式(Publish/Subscribe):定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
1.3 数据通信
2. 一个简单的案例
2.1 PureMVC架构基本的开发流程:
- 定义自己的ApplicationFacade类(继承Facade)
1)注册控制层类,以及定义“消息名称”与命令类的绑定关系
2)注册模型层类
3)注册视图层类 - 定义自己的类
1)模型层类(继承Proxy)
2)视图层类(继承Mediator)
3)控制层类 (继承SimpleCommand或者MacroCommand) - 确定层之间的消息传递。
- 调试项目。
2.2 案例介绍
demo地址:https://gitee.com/ZhaoYongshuang/puremvc_framework_demo
2.3 代码实现
2.3.1 ApplicationFacade.cs
using PureMVC.Patterns;
using PureMVC.Interfaces;
public class ApplicationFacade :Facade
{
public new static IFacade Instance
{
get
{
if (m_instance == null)
{
lock (m_staticSyncRoot)
{
if (m_instance == null)
{
m_instance = new ApplicationFacade();
}
}
}
return m_instance;
}
}
protected override void InitializeModel()
{
base.InitializeModel();
RegisterProxy(new MyProxy());
}
protected override void InitializeView()
{
base.InitializeView();
RegisterMediator(new MyMediator());
}
protected override void InitializeController()
{
base.InitializeController();
RegisterCommand("Cmd_StartUpApp", typeof(CmdStartUpApp));
RegisterCommand("Cmd_AddCount", typeof(CmdAddCount));
}
}
2.3.2 MyUI.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class MyUI : MonoBehaviour
{
private Text txtLevel;
private Button btnAddCount;
public Action actionBtnCount;
void Start()
{
txtLevel = transform.Find("TxtCount").gameObject.GetComponent<Text>();
btnAddCount = transform.Find("BtnCount").gameObject.GetComponent<Button>();
btnAddCount.onClick.AddListener(OnClickAddCount);
}
private void OnClickAddCount()
{
actionBtnCount?.Invoke();
}
public void SetTxtCount(MyData myData)
{
txtLevel.text = myData.Count.ToString();
}
}
2.3.3 StartGame.cs
using UnityEngine;
public class StartGame : MonoBehaviour {
public MyUI myUI;
void Start()
{
ApplicationFacade appFacade = ApplicationFacade.Instance as ApplicationFacade;
if (appFacade != null && myUI != null)
{
appFacade.SendNotification("Cmd_StartUpApp", myUI);
}
}
}
2.3.4 CmdStartUpApp.cs
using PureMVC.Interfaces;
using PureMVC.Patterns;
public class CmdStartUpApp : SimpleCommand
{
public override void Execute(INotification notification)
{
MyUI myUI = notification.Body as MyUI;
if(myUI != null)
{
SendNotification("Msg_InitMyUI",myUI);
}
}
}
2.3.5 MyMediator.cs
using System.Collections.Generic;
using PureMVC.Interfaces;
using PureMVC.Patterns;
public class MyMediator : Mediator {
public new const string NAME = "MyMediator";
private MyUI myUI
{
get
{
return base.ViewComponent as MyUI;
}
}
public override IList<string> ListNotificationInterests()
{
IList<string> listResult=new List<string>();
listResult.Add("Msg_InitMyUI");
listResult.Add("Msg_AddCount");
return listResult;
}
public override void HandleNotification(INotification notification)
{
switch (notification.Name)
{
case "Msg_InitMyUI":
MyUI myUI = notification.Body as MyUI;
this.InitMyUIMediator(myUI);
break;
case "Msg_AddCount":
MyData myData = notification.Body as MyData;
this.AddCount(myData);
break;
default:
break;
}
}
private void InitMyUIMediator(MyUI myUI)
{
if (myUI != null)
{
base.m_mediatorName = NAME;
base.m_viewComponent = myUI;
myUI.actionBtnCount = this.OnClickAddCount;
}
}
private void OnClickAddCount()
{
SendNotification("Cmd_AddCount");
}
private void AddCount(MyData myData)
{
this.myUI.SetTxtCount(myData);
}
}
2.3.6 CmdAddCount.cs
using PureMVC.Interfaces;
using PureMVC.Patterns;
public class CmdAddCount :SimpleCommand {
public override void Execute(INotification notification)
{
MyProxy myProxy = (MyProxy)Facade.RetrieveProxy(MyProxy.NAME);
myProxy.AddLevel(10);
}
}
2.3.7 MyData.cs
public class MyData
{
private int _count = 0;
public int Count
{
get { return _count; }
set { _count = value; }
}
}
2.3.8 MyProxy.cs
using PureMVC.Patterns;
public class MyProxy :Proxy {
public new const string NAME = "MyProxy";
private MyData myData = null;
public MyProxy():base(NAME)
{
myData = new MyData();
}
public void AddLevel(int addNumber)
{
myData.Count += addNumber;
SendNotification("Msg_AddCount", myData);
}
}
2.4 消息流程
3 框架源码分析
源码在上面的demo中有,可自行下载查看
3.1 源码文件结构
3.2 源码消息流程
- Controller保存了Command与Notification之间的映射。当Notification(通知)被发出时,对应的Command(命令)就会自动地由Controller执行。Command实现复杂的交互,降低View和Model之间的耦合度。
- Mediator发送、声明、接收Notification。当用View注册Mediator时,Mediator的ListNotificationInterests方法会被调用,以list形式返回该Mediator对象所关心的所有Notification。之后,当系统其它角色发出同名的Notification(通知)时,关心这个通知的Mediator都会调用handleNotification方法并将Notification以参数传递到方法。
- Proxy发送,但不接收Notification。在很多场合下Proxy需要发送Notification(通知),比如:Proxy从远程服务器接收到数据时,发送Notification告诉系统;或当Proxy的数据被更新时,发送Notification告诉系统。