基于C#委托的观察者模式

最近为了做一个卡牌的项目,写了个观察模式的工具类来处理卡牌技能的效果,主要的功能是封装了发送者(sender)的筛别,可以在消息内容筛别的基础上,用发送者作为进一步筛别的条件,并且可以将自己作为传参。把原本一对多的委托,封装为多对多。

工具类本身还没有正式投入使用,可能还存在并发方面的bug

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Mekong.Notifications
{
    /// <summary>
    /// This delegate is similar to an EventHandler:
    ///     The first parameter is the sender, 
    ///     The second parameter is the arguments / info to pass
    /// </summary>
    using Handler = System.Action<System.Object, Component>;
    
    /// <summary>
    /// The SenderTable maps from an object (sender of a notification), 
    /// to a List of Handler methods
    ///     * Note - When no sender is specified for the SenderTable, 
    ///         the NotificationCenter itself is used as the sender key
    /// </summary>
    using SenderTable = System.Collections.Generic.Dictionary<System.Object, System.Action<System.Object, Component>>;

    public class NotificationCenter : UnitySingleton<NotificationCenter>
    {
        #region Properties
        /// <summary>
        /// The dictionary "key" (string) represents a notificationName property to be observed
        /// The dictionary "value" (SenderTable) maps between sender and observer sub tables
        /// </summary>
        private Dictionary<string, SenderTable> _table = new Dictionary<string, SenderTable>();
        //private HashSet<List<Handler>> _invoking = new HashSet<List<Handler>>();
        #endregion

        #region Singleton Pattern
        private NotificationCenter() { }
        #endregion

        #region Public
        public void AddObserver(Handler handler, string notificationName)
        {
            AddObserver(handler, notificationName, null);
        }

        public void AddObserver(Handler handler, string notificationName, System.Object sender)
        {
            if (handler == null)
            {
                Debug.LogError("Can't add a null event handler for notification, " + notificationName);
                return;
            }

            if (string.IsNullOrEmpty(notificationName))
            {
                Debug.LogError("Can't observe an unnamed notification");
                return;
            }

            if (!_table.ContainsKey(notificationName))
                _table.Add(notificationName, new SenderTable());

            SenderTable subTable = _table[notificationName];

            System.Object key = (sender != null) ? sender : this;

            if (!subTable.ContainsKey(key))
            {
                Handler MyDelegate = null;
                subTable.Add(key, MyDelegate);
            }

            Debug.Log(key);

            subTable[key] += handler;
        }

        public void RemoveObserver(Handler handler, string notificationName)
        {
            RemoveObserver(handler, notificationName, null);
        }

        public void RemoveObserver(Handler handler, string notificationName, System.Object sender)
        {
            if (handler == null)
            {
                Debug.LogError("Can't remove a null event handler for notification, " + notificationName);
                return;
            }

            if (string.IsNullOrEmpty(notificationName))
            {
                Debug.LogError("A notification name is required to stop observation");
                return;
            }

            // No need to take action if we dont monitor this notification
            if (!_table.ContainsKey(notificationName))
                return;

            SenderTable subTable = _table[notificationName];
            System.Object key = (sender != null) ? sender : this;

            if (!subTable.ContainsKey(key))
                return;

            subTable[key] -= handler;
        }

        /// <summary>
        /// Clean up the table
        /// If one specific notofications or sender's delegate which is null,then delete it 
        /// </summary>
        public void Clean()
        {
            string[] notKeys = new string[_table.Keys.Count];
            _table.Keys.CopyTo(notKeys, 0);

            for (int i = notKeys.Length - 1; i >= 0; --i)
            {
                string notificationName = notKeys[i];
                SenderTable senderTable = _table[notificationName];

                object[] senKeys = new object[senderTable.Keys.Count];
                senderTable.Keys.CopyTo(senKeys, 0);

                for (int j = senKeys.Length - 1; j >= 0; --j)
                {
                    object sender = senKeys[j];
                    Handler handlers = senderTable[sender];
                    if (handlers == null)
                        senderTable.Remove(sender);
                }

                if (senderTable.Count == 0)
                    _table.Remove(notificationName);
            }
        }

        public void PostNotification(string notificationName)
        {
            PostNotification(notificationName, null);
        }

        public void PostNotification(string notificationName, Component e)
        {
            PostNotification(notificationName, null, e);
        }

        public void PostNotification(string notificationName, System.Object sender, Component e)
        {
            if (string.IsNullOrEmpty(notificationName))
            {
                Debug.LogError("A notification name is required");
                return;
            }

            // No need to take action if we dont monitor this notification
            if (!_table.ContainsKey(notificationName))
                return;

            // Post to subscribers who specified a sender to observe
            SenderTable subTable = _table[notificationName];
            if (sender != null && subTable.ContainsKey(sender))
            {
                Handler handlers = subTable[sender];
                handlers(sender, e);
                return;
            }

            // Post to subscribers who did not specify a sender to observe
            if (subTable.ContainsKey(this))
            {
                Handler handlers = subTable[this];
                handlers(sender, e);
                return;
            }
        }
        #endregion
    }
}

用法示例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mekong.Notifications;

public class Test1 : MonoBehaviour {

    void Start() { 
        NotificationCenter.Instance().AddObserver(OnPushButton,"Push Hello Button");
    }

    void OnPushButton(object sender, Component argv) {
        Debug.Log(argv.GetComponent<Test2>().number);
    }
}

public class Test2 : MonoBehaviour
{
    public int number = 114125;

    void Update()
    {
        if (Input.anyKeyDown)
        {
            NotificationCenter.Instance().PostNotification("Push Hello Button", this);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/keven2148/article/details/80423153