[Unity]PureMVC框架解读(下)

PureMVC框架解读

我们先讲解一下简单事件系统和PureMVC中的命令/通知系统做个比较。

1.简单事件系统

事件系统是委托的典型用法,C#委托包含Action、delegate、Func、predicate几种类型,具体的用法可以去百度查阅一下其他资料,这里我们先简单讲解一下事件系统。事件系统在Unity中可以用来解耦视图与模型,使得视图和模型重用性都有所提升。Unity WIKI这里有很多变种的事件系统。

1.1 什么是事件系统

简单讲就是利用字典记录方法,执行事件系统就是调用已经记录的方法。

  • 字典记录事件集合
  • 执行事件的接口
  • 注册事件的接口
  • 注销事件的接口
  • 清空事件的接口
  • 接口通常会有几个重载方法,实现不同的参数数量

1.2 事件系统代码

以下是一个简易事件系统的模板:

public class EventSystem
{
    //事件字典
    private static readonly Dictionary<string , Delegate> Events = new Dictionary<string, Delegate>();   

    //执行事件的重载方法
    public static void Invoke(string eventName)
    {
            foreach (Delegate @delegate in invoke(eventName))
                @delegate.DynamicInvoke();
    }
    public static void Invoke<T>(string eventName,T argt ){}

    //执行事件异常检查
    private static Delegate[] invoke( string eventName )
    {
        if (!Events.ContainsKey(eventName))
            UnityEngine.Debug.LogError(string.Format("Can not get the {0} event!",eventName));
        Delegate @delegate = Events[eventName];
        return @delegate.GetInvocationList();
    }

    //注册事件的重载方法
    public static void Register( string eventName, Action action )
    {
        register(eventName);
        Events[eventName] = (Action)Events[eventName] + action;
    }
    public static void Register<T>( string eventName , Action<T> action ){}

    //注册事件
    private static void register( string eventName )
    {
        if (!Events.ContainsKey(eventName))
            Events.Add(eventName, null);
    }

    //注销事件的重载方法
    public static void UnRegister( string eventName , Action action )
    {
        register(eventName);
        Events[eventName] = (Action)Events[eventName] - action;
    }

    //清楚事件的重载方法
    public static void Clear( string eventName )
    {
        if (Events.ContainsKey(eventName))
            Events[eventName] = null;
    }
}

1.3 事件系统案例

结合一个小的案例看一下简单的事件系统的使用:

internal class TestClass : MonoBehaviour
{
    public const string EVENT_NAME = "EventName";
    private void Awake()
    {
        TestEvent test = new TestEvent();
        //注册事件
        EventSystem.Register(EVENT_NAME,test.Invoke);
    }
}

internal class TestEvent
{
    public void Invoke()
    {
        Debug.Log("Invoke");
    }
}

internal class TestInvoker : MonoBehaviour
{
    public const string EVENT_NAME = "EventName";
    public void Start()
    {
        //执行事件,即可执行以及注册对应的事件
        SpringFramework.Event.EventSystem.Invoke(EVENT_NAME);
    }
}

event

通过以上简述大家应该对简易事件系统有个了解,接下里我们看看PureMVC中的命令/通知系统,功能和简易事件系统一样实现部分代码之间的解耦,让方法调用更加的便捷。

2.PureMVC通知系统

2.1 PureMVC通知系统与简易事件系统的区别

  • 业务拆分更加细致,通知内容(Notificatoin:INotification),通知发送者(Notifer:INotifer),通知执行者(Observer:IObserver)全部都拆分为具体的类型,使得整个系统的拓展性更强
  • Notification作为单独类型可自由定义通知内容,拓展方便简单,简易事件系统拓展会受到参数数量限制,导致拓展复杂
  • 简易事件系统参数是通过泛型方法来定义的,但是PureMVC中将参数装箱为object类型,然后在执行时拆箱为对应类型,虽然装箱拆箱消耗了一定的性能,但是使得参数传递变得更加简单,方便

2.2PureMVC通知系统代码分析

通知系统大概拆分为通知发送者(Notifer),通知内容(Notification),通知观察者/执行者(Observer)

2.2.1 PureMVC通知系统核心代码分析

  • Notifer:INotifer 发送通知的方法
public interface INotifier
{
    //发送通知的重载方法
    void SendNotification(string notificationName);
    void SendNotification(string notificationName, object body);
    void SendNotification(string notificationName, object body, string type);
}

public class Notifier : INotifier
{
    //保存Facade的实例,通知通过外观Facade通知给View(外观者保存了MVC三个模块的实例),View记录了所有的观察者,然后遍历观察者找到对应的观察者,通知观察者执行通知
    private IFacade m_facade = PureMVC.Patterns.Facade.Instance;

    public void SendNotification(string notificationName)
    {
        this.m_facade.SendNotification(notificationName);
    }
    public void SendNotification(string notificationName, object body)
    {
        this.m_facade.SendNotification(notificationName, body);
    }
    public void SendNotification(string notificationName, object body, string type)
    {
        this.m_facade.SendNotification(notificationName, body, type);
    }

    protected IFacade Facade
    {
        get
        {
            return this.m_facade;
        }
    }
}
  • Notification:INotification 通知的具体内容
public interface INotification
{
    //重写通知ToString,用于调试输出
    string ToString();
    //通知事件
    object Body { get; set; }
    //通知名称
    string Name { get; }
    //通知类型 
    string Type { get; set; }
}
//Notification只是实现了接口中的内容
public class Notification : INotification
{
    private object m_body;
    private string m_name;
    private string m_type;
    public Notification(string name) : this(name, null, null){}
    public Notification(string name, object body) : this(name, body, null){}
    public Notification(string name, object body, string type)
    {
        this.m_name = name;
        this.m_body = body;
        this.m_type = type;
    }
    public override string ToString()
    {
        return ((("Notification Name: " + this.Name) + "\nBody:" + ((this.Body == null) ? "null" : this.Body.ToString())) + "\nType:" + ((this.Type == null) ? "null" : this.Type));
    }
    public object Body
    {
        get
        {
            return this.m_body;
        }
        set
        {
            this.m_body = value;
        }
    }
    public string Name
    {
        get
        {
            return this.m_name;
        }
    }
    public string Type
    {
        get
        {
            return this.m_type;
        }
        set
        {
            this.m_type = value;
        }
    }
}
  • Observer : IObserver 观察者/执行者,根据通知内容反射得到中介者和命令的方法,然后传参数执行
public interface IObserver
{
    //对比NotifyContext
    bool CompareNotifyContext(object obj);
    //通知观察者
    void NotifyObserver(INotification notification);
    //记录是Mediator或Command
    object NotifyContext { set; }
    //通知方法
    string NotifyMethod { set; }
}

public class Observer : IObserver
{
    //...其他的字段和方法
    public void NotifyObserver(INotification notification)
    {
        object notifyContext;
        lock (this.m_syncRoot)
        {
            notifyContext = this.NotifyContext;
        }
        //利用反射获取方法然后执行
        Type type = notifyContext.GetType();
        //这里设置忽略字母的大小写|公共成员|实例成员
        BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
        //根据设置的中介者的名字或者是命令的名字执行对应的方法
        //如果notifyContext是中介者(Mediator)方法名是HandleNotification
        //notifyContext是命令方法名是ExecuteCommand
        //HandleNotification和ExecuteCommand在注册中介者和命令时构造的Observer的名字为notifyContext或HandleNotification
        MethodInfo method = type.GetMethod(this.NotifyMethod, bindingAttr);
        method.Invoke(notifyContext , new object[] { notification });
    }
}

2.2.2 PureMVC通知系统与MVC结合代码分析


  • 注册

因为观察者/执行者是在注册中介者或者是命令的时候构造并存入字典的,在View.RegisterMediator方法中构造观察者并写入字典,命令的注册时在Controller中执行的,Controller又通过View的实例调用View中的RegisterObserver注册命令的观察者/执行者

public class View : IView
{
    protected IDictionary<string, IMediator> m_mediatorMap = new Dictionary<string, IMediator>();
    //观察者字典
    protected IDictionary<string, IList<IObserver>> m_observerMap = new Dictionary<string, IList<IObserver>>();

    public virtual void RegisterMediator(IMediator mediator)
    {
        lock (this.m_syncRoot)
        {
            if (this.m_mediatorMap.ContainsKey(mediator.MediatorName))
            {
                return;
            }
            this.m_mediatorMap[mediator.MediatorName] = mediator;
            //获取中介者的通知列表
            IList<string> list = mediator.ListNotificationInterests();
            if (list.Count > 0)
            {
                IObserver observer = new Observer("handleNotification", mediator);
                for (int i = 0; i < list.Count; i++)
                {
                    //将通知名注册给观察者
                    this.RegisterObserver(list[i].ToString(), observer);
                }
            }
        }
        mediator.OnRegister();
    }

    public virtual void RegisterObserver(string notificationName, IObserver observer)
    {
        lock (this.m_syncRoot)
        {
            if (!this.m_observerMap.ContainsKey(notificationName))
            {
                //字典key存储通知名称 value存储观察者
                this.m_observerMap[notificationName] = new List<IObserver>();
            }
            this.m_observerMap[notificationName].Add(observer);
        }
    }
}

//命令的注册
public class Controller : IController
{
    // 记录命令的类型
    protected IDictionary<string, Type> m_commandMap = new Dictionary<string, Type>();
    protected IView m_view;

    public virtual void RegisterCommand(string notificationName, Type commandType)
    {
        lock (this.m_syncRoot)
        {
            if (!this.m_commandMap.ContainsKey(notificationName))
            {
                this.m_view.RegisterObserver(notificationName, new Observer("executeCommand", this));
            }
            this.m_commandMap[notificationName] = commandType;
        }
    }
}
  • 执行

通过Notifer(执行者)我们知道通知的发送是通过Facade.m_view.NotifyObservers()方法发出的

public class View : IView
{
    public virtual void NotifyObservers(INotification notification)
    {
        IList<IObserver> list = null;
        lock (this.m_syncRoot)
        {
            if (this.m_observerMap.ContainsKey(notification.Name))
            {
                IList<IObserver> collection = this.m_observerMap[notification.Name];
                //获取到通知已经注册的所有观察者
                list = new List<IObserver>(collection);
            }
        }
        if (list != null)
        {
            for (int i = 0; i < list.Count; i++)
            {
                //遍历观察者并执行观察者中的方法,通过反射获取方法执行HandleNotification或者ExecuteCommnd
                //ExecuteCommand是Controller中的方法,它会遍历所有的命令类型找到对应的命令然后执行Execute方法
                list[i].NotifyObserver(notification);
            }
        }
    }
}

执行的方法类似以下:

 public class ClientMediator : Mediator
 {
    public override void HandleNotification(INotification notification)
    {
        switch (notification.Name)
        {
            case OrderSystemEvent.CALL_WAITER:
                ClientItem client = notification.Body as ClientItem;
                if(null == client)
                    throw new Exception("对应桌号顾客不存在,请核对!");
                Debug.Log(client.id + " 号桌顾客呼叫服务员 , 索要菜单 ");
                break;
            case OrderSystemEvent.ORDER: 
                Order order1 = notification.Body as Order;
                if(null == order1)
                    throw new Exception("order1 is null ,please check it!");
                order1.client.state++;
                View.UpdateState(order1.client);
                break;
            case OrderSystemEvent.PAY:
                Order finishOrder = notification.Body as Order;
                if ( null == finishOrder )
                    throw new Exception("finishOrder is null ,please check it!");
                finishOrder.client.state++;
                View.UpdateState(finishOrder.client);
                SendNotification(OrderSystemEvent.GET_PAY, finishOrder);
                break;
        }
    }
 }

internal class StartUpCommand : SimpleCommand
{
    public override void Execute(INotification notification)
    {
        //菜单代理
        MenuProxy menuProxy = new MenuProxy();
        Facade.RegisterProxy(menuProxy);

        //客户端代理
        ClientProxy clientProxy = new ClientProxy();
        Facade.RegisterProxy(clientProxy);

        //服务员代理
        WaiterProxy waitProxy = new WaiterProxy();
        Facade.RegisterProxy(waitProxy);

        //厨师代理
        CookProxy cookProxy = new CookProxy();
        Facade.RegisterProxy(cookProxy);

        OrderProxy orderProxy = new OrderProxy();
        Facade.RegisterProxy(orderProxy);

        MainUI mainUI = notification.Body as MainUI;

        if(null == mainUI)
            throw new Exception("程序启动失败..");
        Facade.RegisterMediator(new MenuMediator(mainUI.MenuView));
        Facade.RegisterMediator(new ClientMediator(mainUI.ClientView)); 
        Facade.RegisterMediator(new WaiterMediator(mainUI.WaitView));
        Facade.RegisterMediator(new CookMediator(mainUI.CookView));
    }
}

2.2.3 PureMVC通知系统代码总结

  • PureMVC中通知的执行分为两种:中介者(Mediator)和具体的命令(Command),中介者是面向视图(View)的执行者,调用INotification.HandleNotification方法来执行具体的操作,命令是面向控制器(Controller)的执行者,调用Execute来执行具体的操作,本质是一样的,但是可以区分一下两只的使用环境,中介者用于视图方面的通知和其他中介者之间的交互,但是命令应该用于系统功能级别,比如启动程序,或者是关闭程序等

  • PureMVC中通过反射获取观察者的类型来区分中介者、命令这两种不同的通知类型

PureMVC框架总结

通过上一篇讲解核心MVC类和这一篇通知系统的讲解,大家应该对PureMVC有了一个大概的理解,通过看我Github的案例代码,应该就可以入手PureMVC框架了,下面做一个PureMVC的总结。
- PureMVC是一个轻量级架构,但是它却可以有效解耦,提供编码效率,提升部分代码重用
- PureMVC对于超小型项目可能会导致代码过于繁琐,但是只要是团队开发,PureMVC可以帮你避免掉很多不规范
- PureMVC也是一种较为容易理解运行机制的框架,即便是新手也可以很快入门,在团队中还是值得使用的

下一次我将解读StrangeIOC控制反转,依赖注入的框架,其中内容更值得我们学习,想要了解的同学可以关注我哦!

Unity自定义UI组件系列

Unity框架解读系列

分享地址

如果你想了解unity框架的更多知识,欢迎关注我的博客,我会持续更新,支持一下我这个博客新手。如果以上文章对你有帮助,点个赞,让更多的人看到这篇文章我们一起学习。如果有什么指点的地方欢迎在评论区留言,秒回复。

猜你喜欢

转载自blog.csdn.net/qq_29579137/article/details/73717882