观察者模式的原理以及在unity中的具体应用

      做程序也算是有一段时间了,对于之前还是没有深刻理解编程。在纪超大神的指导下,开始研究设计模式以及设计模式的具体运用。通过一些书籍和资料,进行归纳总结,这些例子大多数不是我自己的,我会标明出处,我只是把这些案例更加通俗的写出来,为自己做一个关于设计模式的专题,一方面是自己学习,一方面是和大家分享。从开始接触编程,到对编程设计模式的理解和运用是编程之路中不可或缺的一步。

       这一篇也算是第一章节,我们从观察者模式开始

       我们这里首先不谈太高深的概念性的东西

所谓观察者模式,好比股票的买卖,就如京东的股市行情,这是一个对象,也是消息的发送者Sender。股民买家都会去观察京东股票的涨幅和跌幅。这个涨幅和跌幅是推送给所有买家的,买家在接收到涨幅和跌幅的信息后,进行自己的操作。

       这就是观察者模式,或者叫消息订阅模式。这种模式的主旨就是消息的一对多的推送,观察者根据得到的消息进行更新和更改。这些需要用到C#中委托和事件,希望不太理解委托事件的,可以好好研究一些,在unity开发中会经常用到。现在回想,unity很多底层的机制,特别是UGUI这一块都是按照这个思路来的。

        首先我们从一个简单的例子开始,有这样一个Scene,这个例子的出处我在代码注释中已经标明。


在这个场景中,当我点击Button之后,Button1的底色变红,Text1的txt会变为hi,Eagle1,Text1的txt会变为hi,Eagle2。

对于这样一个功能,我们首先要明确谁是消息的观察者,谁是消息的推送者。显然,Button1和Text1、Text2是消息的订阅者也就是观察者。Button是消息的推送者。我们通过委托事件的绑定来实现这样一个过程。以下我会给出四个脚本,分别处理一个Sender和三个Observer的功能。

在Sender的脚本中,你必须有一个事件委托机制,进行消息的传递,也就是需要一个机制,触发观察者的行为。 


然后我们通过三个观察者对于事件的绑定

观察者1


观察者2


观察者3


我们把这四个脚本都挂在到Canvas上,效果如下


当点击Button之后,其它三个观察者发生变化。

通过以上这个简单的案例,相信大家对于观察者模式有了一定理解。

接下来我这里给出一个观察者模式中稍微复杂一点的小框架,接下来的案例中,通过Slider的滑动,改变小球的值。其实就是消息的传送过程。

事件机制可以理解为在一个事件中心(Subject)保存有对所有事件(Observer)的引用,事件中心负责对这些事件进行分发,这样每个事件就可以通过回调函数的方式进行更新,这样就实现了一个事件机制。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
//注意到在这个“通知中心”中,我们首先实现了单例模式,这样我们可以通过Get方法来获取该“通知中心”的唯一实例
//其次这里利用一个字典来存储对所有事件的引用,这样保证外部可以通过AddEventListener和RemoveEventListener这两个方法来进行事件的添加和移除
//对于添加的事件引用我们可以通过DispatchEvent方法来分发一个事件,事件的回调函数采用委托来实现,注意到这个委托需要一个Notification类型
//对该类型简单定义在另外一个脚本中

namespace UniEventDispatcher {

    //定义一个OnNotification类型的时间分发委托  事件最终是需要绑定东西的

    public delegate void OnNotification(Notification notific);

    public class NotificationCenter     //通知中心
    {
        private static NotificationCenter instance = null;          

        public static NotificationCenter Get() //单例   返回一个NotificationCenter类型的对象
        {
                if(instance == null)
                {
                       instance = new NotificationCenter();
                       return instance;
                }
                return instance;
        }

        //储存事件的字典  字典只是规定了键值对的类型
        private Dictionary<string, OnNotification> eventListeners = new Dictionary<string, OnNotification>();

        /// <summary>
        /// 注册事件
        /// </summary>
        /// <param name = "eventKey">事件</param>
        /// <param name = "eventListener">事件监听器</param>
        public void AddEventListener(string eventKey,OnNotification eventListener)
        {
                if (!eventListeners.ContainsKey(eventKey))
                {
                       eventListeners.Add(eventKey, eventListener);
                }
        }

        /// <summary>
        /// 移除事件
        /// </summary>
        /// <param name = "eventKey">事件Key</param>
        public void RemoveEventListener(string eventKey) //如果字典不包含就直接返回,如果包含就直接删除
        {
                  if (!eventListeners.ContainsKey(eventKey))
                 {
                        return;
                  }
                  else
                  {
                        eventListeners[eventKey] = null;
                        eventListeners.Remove(eventKey);
                  }
        }

        /// <summary>
        /// 分发事件
        /// </summary>
        /// <param name = "eventKey">事件Key</param>
        /// <param name = "notific">通知</param> 需要进行通知的内容  相当于把事件绑定具体方法
        public void DispatchEvent(string eventKey,Notification notific)
        {
                  if (!eventListeners.ContainsKey(eventKey)) return;
                  eventListeners[eventKey](notific);  //相当于具体的事件OnNotification eventListener 绑定 特定的方法
        }

        /// <summary>
        /// 分发事件
        /// </summary> 
        /// <param name = "eventKey">事件Key</param>
        /// <param name = "sender">发送者</param> 
        /// <param name = "param">通知内容</param>
        public void DispatchEvent(string eventKey,GameObject sender,object param)
        {
                 if (!eventListeners.ContainsKey(eventKey)) return;
                 eventListeners[eventKey](new Notification(sender,param));
        }

        /// <summary>
        /// 分发事件
        /// </summary> 
        /// <param name = "eventKey">事件Key</param>
        /// <param name = "param">通知内容</param>
        public void DispatchEvent(string eventKey,object param)
        {
                if (!eventListeners.ContainsKey(eventKey)) return;
                eventListeners[eventKey](new Notification(param));
        }

        /// <summary>
        /// 是否存在指定事件的监听器
        /// </summary> 
        public Boolean HasEventListener(string eventKey)
        {
               return eventListeners.ContainsKey(eventKey);
        }
    }

}

注意到在这个“通知中心”中,我们首先实现了单例模式,这样我们可以通过Get方法来获取该“通知中心”的唯一实例,其次这里利用一个字典来存储对所有事件的引用,这样保证外部可以通过AddEventListener和RemoveEventListener这两个方法来进行事件的添加和移除,对于添加的事件引用我们可以通过DispatchEvent方法来分发一个事件,事件的回调函数采用委托来实现,注意到这个委托需要一个Notification类型。

//对Notification的定义需要提供发送者和发送内容,这样可以保证所有的通知都按照这样的格式进行定义,
//如果有Socket开发经验的朋友可能会联想到通讯协议的定义,这里是比较相似
//我们限定了创建Notification对象,当你创建Notification类型对象的时候需要带有怎样的参数
namespace UniEventDispatcher
{
    public class Notification
    {
        ///<summary>
        ///通知发送者
        ///</summary>
        public GameObject sender;


        ///<summary>
        ///通知的内容
        /// 备注:在发送消息时需要装箱、解析消息时需要拆箱
        /// 所以这是一个糟糕的设计,需要注意。
        ///</summary>
        public object param;


        ///<summary>
        ///构造函数
        ///</summary>
        ///<param name = "sender">通知发送者</param>
        ///<param name ="param">通知内容</param>
        public Notification(GameObject sender,object param)
        {
            this.sender = sender;
            this.param = param;
        }


        ///<summary>
        ///构造函数
        ///</summary>
        ///<param name ="param"></param>
        public Notification(object param)
        {
            this.sender = null;
            this.param = param;
        }


        ///<summary>
        ///实现ToString方法
        ///</summary>
        ///<returns></returns>
        public override string ToString()
        {
            return string.Format("sender = {0},param = {1}",this.sender,this.param);
        }
    }

}

我们用一个如上面所说的小球颜色变换的例子来诠释上面这两个脚本的运用过程。在这个示例中UI是事件发送者,负责UI中Slider控件的数值发生变化时向球体发送消息,传递的数据类型是Color类型,球体为事件接收者,负责注册事件及接收到消息后的处理。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UniEventDispatcher;
using System;

public class Example : MonoBehaviour {
    /// <summary>
    /// R数值的Slider
    /// </summary>
    private Slider sliderR;

    /// <summary>
    /// G数值的Slider
    /// </summary>
    private Slider sliderG;

    /// <summary>
    /// B数值的Slider
    /// </summary>
    private Slider sliderB;

void Start () {
        //在接受者中注册事件及回调方法  把ChangeColor(Notification notific)存放到字典中,必须是OnNotification类型的函数方法
        NotificationCenter.Get().AddEventListener("ChangeColor",ChangeColor);

        //在发送者中分发事件,这里以UI逻辑为例子
        sliderR = GameObject.Find("Canvas/SliderR").GetComponent<Slider>();
        sliderG = GameObject.Find("Canvas/SliderG").GetComponent<Slider>();
        sliderB = GameObject.Find("Canvas/SliderB").GetComponent<Slider>();

        //注册UI事件  为Slider添加UI事件
        sliderR.onValueChanged.AddListener(OnValueChanged);
        sliderG.onValueChanged.AddListener(OnValueChanged);
        sliderB.onValueChanged.AddListener(OnValueChanged);
    }

    private void OnValueChanged(float arg0)
    {
        float r = sliderR.value;
        float g = sliderG.value;
        float b = sliderB.value;
        NotificationCenter.Get().DispatchEvent("ChangeColor",new Color(r,g,b));
    }

    public void ChangeColor(Notification notific)  //这个事件或者说这个方法是用来改变材质的颜色的
    {
        Debug.Log(notific.ToString());
        //设置颜色
        GetComponent<Renderer>().material.color = (Color)notific.param;
    }

}

效果如下:


第一个案例中,如果应用第二个案例的框架,代码如下,有一个问题就是,用框架的情况下,涉及到消息传递过程中的参数需要沟通协调。有一些情况会有一定出入。


以上内容是个人技术分析和讲解,如有不足,可以讨论联系。

冰山先生   QQ:857210494     WeChat:jiangxiang857210494

猜你喜欢

转载自blog.csdn.net/qq_38651460/article/details/79442496