PureMVC

参考

官网:http://puremvc.org/

下载:https://github.com/PureMVC/puremvc-csharp-standard-framework (注意官网点击C#按钮跳转的是multicore版本,这个版本用的是.netcore的库,导入unity编译会报错,因为有很多API不一样,例如Lazy等)

下载好后,需要导入Unity的文件有PureMVC目录下的Core,Interfaces,Patterns三个目录。

官方文档:http://puremvc.org/docs/PureMVC_IIBP_Chinese.pdf

参考文章:PureMVC框架在Unity中的应用(一)

PureMVC框架在Unity中的应用(二)

 

简介

PureMVC就是在MVC的基础上新增了一些设计模式。有关MVC的就不多介绍了,简单说就是将功能模块分成三个部分Model(数据),View(视图),Controller(控制器,逻辑处理)。PureMVC在此基础上新增了Mediator来处理View,Command来处理Controller,Proxy来处理Model,同时还有Facade做一个整体管理,Notification用于相互通信(详细的后续介绍)。

用一个简单的案例来说说自己的理解:

假设我们要做一个商城系统,每件商品价格,介绍等就是我们的数据,我们可以用一个类封装起来,也就是Model类。而这些数据我们肯定是从服务器那获取的,这种操作我们需要使用一个Proxy来完成,Proxy访问服务器,拿到数据后给Model赋值。如果有些商品买了之后会下架,移除数据的操作也需要在Proxy中执行。一些数据的增删改查的方法我们可以写在Model类中,但是调用一定要在Proxy中。

商城肯定会有一个界面来显示这些商品,我们需要一个View类来关联界面上的商品列表(List),购买按钮(Button)等。但是具体这个商品列表要显示那些商品,或者购买按钮点击要做哪些操作,这些我们需要新增Mediator类来处理。

接着如果玩家要购买商品,点击购买按钮,这时候就需要我们的Command出场了,例如判断钱够不够,背包满了没有等逻辑都需要在Command中处理。在Command中我们可以找到对应需要的ProxyMediator,来做一些后续的操作。

 

关系图

官方给的各模块之间的关系图如下:不过图中的View,Model,Controller更应该是三个Manager类,和UI,Obj才是我们上面提到的View和Model,不要混淆了。

PureMVC_第1张图片

 

Core

PureMVC中的三个核心类分别是View,Model和Controller,更准确的说应该是ViewManager,ModelManager和ControllerManager。三个类都是单例,与它们所管理的对象关系如下:

Model中保存对Proxy对象的引用

protected readonly ConcurrentDictionary proxyMap;

View保存对Mediator对象的引用

protected readonly ConcurrentDictionary mediatorMap;

Controller保存所有Command 的映射。

rotected readonly ConcurrentDictionary> commandMap;

 

Facade

首先我们先来了解一个设计模式:外观模式(Facade Pattern)。主要作用是隐藏系统的复杂性,提供可以访问系统的接口,属于结构型模式,它向现有的系统添加一系列接口,来隐藏系统的复杂性。

上述系统在PureMVC中就是上面提到的三个核心类,我们在实际使用中,并不需要直接去访问核心类,而是通过PureMVC提供的Facade类来访问。

在实际的应用中,我们需要新建一个继承于Façade类,例如

public class ApplicationFacade : Facade
{
    static ApplicationFacade m_applicationInstance;

    public static ApplicationFacade applicationInstance
    {
        get
        {
            if (m_applicationInstance == null)
            {
                if (instance == null) instance = new ApplicationFacade();
                m_applicationInstance = instance as ApplicationFacade;
            }
            return m_applicationInstance;
        }
    }

    public void Launch()
    {
    }

    protected override void InitializeModel()
    {
        base.InitializeModel();
    }

    protected override void InitializeController()
    {
        base.InitializeController();
    }

}

当我们要注册一个Proxy的时候可以使用下面方法

ApplicationFacade.applicationInstance.RegisterProxy(Proxy);

要获取已注册的Proxy的话则

ApplicationFacade.applicationInstance.RetrieveProxy(ProxyName);

Mediator和Command等同理,这样只需要Facade类就可以实现对三个核心类的访问,也就是外观设计模式的好处。

 

Notification

这又要提到一个设计模式了:观察者模式(Observer Pattern)。可以实现当一个对象被修改时,会自动通知依赖它的对象(观察者),属于行为型模式。例如我们一个界面可能在多个地方显示着金钱数量,当某个行为改变了这个值,只需发送一个通知,这些显示着金钱数量的UI都能够更新到新的值。

PureMVC的通信就是使用观察者模式以一种松耦合的方式来实现的,我们可以使用下面方法在Proxy, Mediator,Command中发送通知。也可以在Facade中直接调用SendNotification发送通知。

//notificationName 唯一id,body和type属于自定义参数
ApplicationFacade.applicationInstance.SendNotification(notificationName, body, type);

在View类中存储了这些观察者,相应的注册删除执行等操作也在该类中。

protected readonly ConcurrentDictionary> observerMap;

此外Command的触发也是通过发送Notification,通过Controller中注册Command的代码可以看出,同样会为Command添加观察者存储在View当中。

public virtual void RegisterCommand(string notificationName, Func factory)
{
    if (commandMap.TryGetValue(notificationName, out _) == false)
    {
        view.RegisterObserver(notificationName, new Observer(ExecuteCommand, this));
    }
    commandMap[notificationName] = factory;
}

Mediator发送、声明、接收Notification:

当注册Mediator(RegisterMediator)时,Mediator的ListNotificationInterests方法会被调用,以数组形式(string[])返回该 Mediator所关心的所有Notification。之后,当系统其它角色发出同名的 Notification时,关心这个通知的Mediator都会调用 HandleNotification方法并将Notification当作参数传递到该方法中。

Proxy发送,但不接收Notification:

在很多场合下Proxy需要发送Notification,比如:当Proxy的数据被更新时,发送Notification告诉系统。 如果让Proxy也侦听 Notification会导致它和View,Controller的耦合度太高。 

 

Command

命令模式(Command Pattern)简单说就是将一块块逻辑代码封装在一个个类中,然后在需要它的时候进行执行。它属于行为型模式。

Command类是无状态的,只在需要时才被创建,并且在被执行(调用Execute方法)之后就会被删除。所以不要在那些生命周期长的对象里引用Command对象。

创建

我们可以通过继承SimpleCommand或者MacroCommand实现Command的创建,SimpleCommand需要重新其Execute方法。

public class SimpleCommandTest : SimpleCommand
{
    public override void Execute(INotification notification)
    {
        Debug.Log("SimpleCommandTest:" + notification.Body);
    }
}

MacroCommand的话可以让我们顺序的执行多个Command,需要重写其InitializeMacroCommand方法,然后使用AddSubCommand添加Command,例如

public class MacroCommandTest : MacroCommand
{
    protected override void InitializeMacroCommand()
    {
        base.InitializeMacroCommand();
        AddSubCommand(() => new SimpleCommandTest());
    }
}

在MacroCommand的Execute方法中会遍历执行通过AddSubCommand添加的Command,源码比较简单,大家可以自己看下,这里就不贴上来了。

注册

由于Command类是无状态的,所以其注册时用的不是Command对象,而是一个Func(创建Command的方式)。

ApplicationFacade.applicationInstance.RegisterCommand("SimpleCommandTest", () => new SimpleCommandTest());
ApplicationFacade.applicationInstance.RegisterCommand("MacroCommandTest", () => new MacroCommandTest());

调用

前面过Command是由发送Notification触发,所以调用SendNotification方法即可。

ApplicationFacade.applicationInstance.SendNotification("SimpleCommandTest", "xyz");
ApplicationFacade.applicationInstance.SendNotification("MacroCommandTest", "123");

这样就会找到对应的Command,并执行其Execute方法。

作用

Command 管理应用程序的业务逻辑(Business Logic),例如应用程序的启动、终止,协调Model与视图的状态等。Command 的业务逻辑应该避免因Model或View的变化而受到影响。

在Command我们可以:

  1. 注册、删除 Mediator、Proxy和Command,或者检查它们是否已经注册。
  2. 发送Notification通知Command或Mediator做出响应。
  3. 获取Proxy对象并直接操作它们。(虽然可以获取Mediator对象并操作,但还是尽量通过发送Notification来处理)

 

Mediator

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。属于行为型模式。

Mediator是View Component(视图组件)与系统其他部分交互的中介器,一个Mediator下可能关联着多个View Component。一个View Component应该把尽可能自己的状态和操作封装起来,对外只提供事件、方法和属性的简单的 API。

创建

例如我们有个界面,显示着一个文本和一个按钮,所谓的视图组件就是一个关联这些界面元素的类。

public class ViewComponent : MonoBehaviour
{
    public Text testText;
    public Button testButton;
}

该界面与外部交互的逻辑,例如改变文本内容,点击触发别的操作,这些操作我们应该写在Mediator中,继承Mediator类即可,在注册完成时会执行其OnRegister方法

public class ViewComponentMediator : Mediator
{
    ViewComponent view;
    public ViewComponentMediator(string mediatorName, object viewComponent = null) : base(mediatorName, viewComponent)
    {
    }

    public override void OnRegister()
    {
        base.OnRegister();
        view = (ViewComponent)ViewComponent;
        
        view.testButton.onClick.AddListener(() =>
        {
            
        });
    }

    public void SetText(string s)
    {
        view.testText.text = s;
    }
}

SetText方法也可以封装在ViewComponent中,由Mediator调用。亦或者按钮的点击事件在ViewComponent中绑定,然后执行一个委托,委托的具体实现在Mediator中处理。

注册

我们先要生成对应的Mediator实例,参数是一个唯一id和对应的GameObject,然后通过Facade进行注册。

ViewComponentMediator mediator = new ViewComponentMediator("ViewComponentMediator", GameObject.Instantiate(viewComponentGameObject));
ApplicationFacade.applicationInstance.RegisterMediator(mediator);

获取

注册好后我们就可以在Command中通过这个唯一id获取到对应的Mediator,然后调用其内部方法对视图进行修改了

ApplicationFacade.applicationInstance.RetrieveMediator("ViewComponentMediator");

Notification

前面提到Mediator可以发送、声明和接收Notification,我们重新其ListNotificationInterests实现声明,重写HandleNotification方法实现接受,例如

public override string[] ListNotificationInterests()
{
    return new string[2] {"Noti1", "Noti2"};
}

public override void HandleNotification(INotification notification)
{
    switch (notification.Name)
    {
        case "Noti1":
            Debug.Log("Noti1");
            break;
        case "Noti2":
            Debug.Log("Noti2");
            break;
    }
}

作用

一个Mediator一般会执行下列操作

  1. 检查事件类型或事件的自定义内容。
  2. 检查或修改View Component的属性(或调用提供的方法)。
  3. 检查或修改Proxy对象公布的属性(或调用提供的方法)。
  4. 发送一个或多个Notification,通知别的Mediatora或Command作出响应(甚至有可能发送给自身)。

因为 Mediator 也会经常和 Proxy 交互,所以我们可以在 Mediator 的构造方法中取得Proxy实例的引用并保存在 Mediator 的属性中,这样避免频繁的获取 Proxy 实例。虽然Mediator可以任意访问Proxy,但是由Command来做这些工作可以实现View和Model之间的松耦合。

经验

在开发中,我们一般可以遵循下面规则:

  1. 如果有多个的Mediator对同一个事件做出响应,那么应该发送一个 Notification,然后相关的 Mediator 做出各自的响应。 
  2. 如果一个Mediator需要和其他的Mediator进行大量的交互,那么一个好方法是利用Command把交互步骤定义在一个地方。
  3. 不应该让一个Mediator直接去获取调用其他的Mediator,在Mediator中定义这样的操作本身就是错误的,应该发送Notification来实现。
  4. Proxy是有状态的,当状态发生变化时发送Notification通知Mediator,将数据的变化反映到视图。
  5. 本质上来讲,在收到一个Notification时Mediator是所要操作的是很少的。有时候,我们需要从Notification里获取有关Proxy的信息。但记住,不应该让处理Notification的方法负责复杂逻辑,业务逻辑应该放在Command中而非在Mediator中。
  6. 如果一个Mediator有太多的对Proxy及其数据的操作,那么,应该把这些代码重构在Command内,简化Mediator,把业务逻辑移放到Command 上,这样Command可以被View的其他部分重用,还会实现View和Model之间的松耦合提高扩展性。
  7. Mediator对外不应该公布操作View Component的函数,而是自己接收Notification做出响应来实现。

View本质上是显示Model的数据并让用户能与之交互,我们期望一种单向依赖,即View依赖于Model,而Model却不依赖于 View。View必须知道Model的数据是什么,但Model却并不需要知道View的任何内容。

 

Proxy

代理模式(Proxy Pattern),一个类代表另一个类的功能。属于结构型模式。

Proxy会提供访问Data Object部分属性或方法的API,也可能直接提供 Data Object 的引用(但不建议这样做,我们应该保护Model,只提供相应的接口来访问)。如果提供了更新Data Object的方法,那么在数据被修改时可能会发送一个Notifidation通知系统的其它部分。

Proxy集中程序的域逻辑(Domain Logic),这么做可以使Model层与相关联的View层、Controller层的分离。

Proxy不监听Notification,也永远不会被通知,因为 Proxy并不关心View的状态。 

Proxy对象不应该通过引用、操作Mediator对象,应该使用发送Notification的方式来通知Mediator,让其知道Data Object(数据对象)发生了改变,使相关的View Component进行相应的更新。

Remote Proxy被用来封装与远程服务的数据访问。Proxy维护那些与Remote service(远程服务)通信的对象,并控制对这些数据的访问。在这种情况下,调用 Proxy 获取数据的方法,然后等待Proxy在收到远程服务的数据后发出异步Notification。

创建

例如我们的商城系统,可能会有一个存储商品信息(id,名称,价格)的类,然后会有个类存放所有的商品信息,并提供一些操作接口,例如:

public class ShopData
{
    public List goodsList;
    
    public ShopData()
    {
        goodsList = new List();
    }
    
    public void AddGoods(int id, string name, int price)
    {
        GoodsData data = new GoodsData(id, name, price);
        goodsList.Add(data);
    }
}

public struct GoodsData
{
    public int goodsId;
    public string goodsName;
    public int goodsPrice;

    public GoodsData(int id, string name, int price)
    {
        goodsId = id;
        goodsName = name;
        goodsPrice = price;
    }
}

操作这些数据的逻辑我们需要在Proxy中进行(继承Proxy),在Proxy中存放数据类(Data Object)的引用,例如:

public class ShopProxy : Proxy
{
    ShopData m_shopData;

    public ShopProxy(string proxyName, object data = null) : base(proxyName, data)
    {
        m_shopData = data as ShopData;
    }

    public void AddGoods(int id, string name, int price)
    {
        m_shopData.AddGoods(id, name, price);
    }
    
    public GoodsData GetGoods(int id)
    {
        return m_shopData.goodsList.Find((a) => (a.goodsId == id));
    }
}

注:我们可以在Proxy中封装一些与服务器通信的方法,使用Proxy来与服务器交互,更新数据。

注册

首先我们要生成一个Proxy实例,并在此时实例化Data Object,然后通过Facade进行注册即可。

ShopProxy proxy = new ShopProxy("ShopProxy", new ShopData());
ApplicationFacade.applicationInstance.RegisterProxy(proxy);

获取

获取已注册的Proxy同样通过Facade即可。

ShopProxy proxy = ApplicationFacade.applicationInstance.RetrieveProxy("ShopProxy") as ShopProxy;

我们可以在Command或者Mediator中获取Proxy,进行数据的更新。

 

 

 

 

 

 

 

 

你可能感兴趣的:(Unity,PureMVC)