本文将从思路和实现讲解基于观察者模式的全局消息机制的实现过程
如果喜欢请给我的博客或者我的项目点个免费的star吧
项目内包含本文全部完整源码(可运行)
一、消息机制
虽然前两篇文章以及写过消息机制是为何物了,但是这里我还是想重申一下,但是稍微简略
- Unity C# 实现简易消息机制
- 消息机制填坑笔记(2)
消息机制用于不同的类、模块之间的通信,让模块之间相互解耦,与消息中心耦合。
A与B之间不再高耦合,而是分别与中心耦合,好处就是当模块数量倍增时,单一模块崩溃不会产生过大的异常,而且方便一个模块对多个模块广播他的命令。
二、观察者模式
在一个神秘的丛林里,有一群老鼠和一只猫,每只老鼠都在观察者猫,猫只要有风吹草动就会被几百只老鼠知道。
这就是观察者模式,我们换一个更简单的例子来说
我们几百个读者都订阅了人民日报公众号,人民日报更新时我们几百个人会收到同样的内容
这也是观察者模式,不过因为是用现实中的软件的实现所以更加贴合我们的程序。
我们可以根据需要选择是否订阅这个公众号,和是否取关,而公众号可以决定是否发布。
这就是观察者模式消息机制的抽象模型
订阅(Subscribe)
取消订阅(UnSubscribe)
发布(Publish)
三、实现方向的确定
泛型:需要写很多泛型的重载代码不好维护,但是用的时候语法很舒服,性能高
元组:需要用户自己元组类型转换,语法难受,性能高
params:方便维护,语法舒服,性能瓶颈严重
前面说到了元组,params,泛型等方式,我们考虑到性能问题,还是选择使用泛型避免装箱拆箱,同时能把最大的舒适度交给用户,用户不用写元组的类型转换了,苦的只是程序员本身,作为一个轮子泛型应该是很合适的了。(让chatgpt帮我生成重复的泛型,233)
前面也说到了
string : 有GC问题,方便但是也不好管理事件总和
enum:需要手动添加,或者工具生成,性能高,容易管理
long:需要写工具生成long,性能高,容易管理
interface: 实现空接口略微比较麻烦,不好管理所有事件,但是比较严谨
object:不做限制有装箱问题,不值得
之前选择的key都或多或少存在各种问题,但是我最终选择的interface,表面上看起来不合适的方法,但是其实这样是合理的,string和object显然是不能选择的,enum和long有很多项目在使用,但是我不想写这个很复杂的工具生成,会让用户手忙脚乱,相比之下接口就是最合适的选择了。
我要求能接受消息的对象必须实现IMessageReceiver,我要求所有消息类型实现IMessage,他们都是空接口,我可以根据这个模型建立一个四层结构
- 管理器(Manager)>派发器(Dispatcher)>事件组(DelegateGroup)>事件集合
其中他们的抽象模型为
-
管理器为 Dictionar<Type,Dispatcher> Type为消息类型,根据消息类型找到该类消息的派发器
-
派发器为 Dictionary<IMessageReceiver,DelegateGroup> 根据接收者实例确定事件组
-
事件表为 Dictionary<Type,List<Delegate.>> ,Type为委托类型以支持重载,根据委托类型确定具体事件集合
Framework.Message.Operator<IAwake>(this).Publish(1, 2, 100.0f);
我可以根据”消息类型“,”接收者“,”参数“来确定所有的定位条件
四、事件组
我们要实现一个存储委托类型和该类型委托列表的字典Dictionary<Type, List> events,但是以下代码不包含对象池,可能不能正常运行,仅作参考
using System;
using System.Collections.Generic;namespace AirFramework
{/// <summary>/// 委托组(方法组委托)/// </summary>public partial class DelegateGroup : Unit{private Dictionary<Type, UnitList<Delegate>> events = new();/// <summary>/// 委托类型数/// </summary>public int Count => events.Count;/// <summary>/// 委托总数(计算重载)/// </summary>public int CountAll{get{int allCount = 0;foreach (var kvp in events){allCount += kvp.Value.Value.Count;}return allCount;}}}
}
namespace AirFramework
{/// <summary>/// 委托组(方法组委托)/// </summary>public partial class DelegateGroup : Unit{internal static Func<UnitList<Delegate>> GetUnitListFromPool = () => Framework.Pool.Allocate<UnitList<Delegate>>();/// <summary>/// 移除该类型委托/// </summary>/// <param name="deleType"></param>public void Remove(Type deleType){events.TryRemoveAndDispose(deleType);}/// 返回同类型可空委托列表,禁止长期持有返回值的引用,该引用在对象池回收后无效,且可能存在元素的数量改变public List<Delegate> Get<DelegateType>(){return GetDelegateList(typeof(DelegateType));}/// <summary>/// 添加委托/// </summary>public void Add(Delegate dele, Type deleType){events.GetValueOrAddDefault(deleType, GetUnitListFromPool).Value.Add(dele);}/// <summary>/// 移除委托/// </summary>public void Remove(Delegate dele, Type deleType){if (events.TryGetValue(deleType, out var kvp)){kvp.Value.Remove(dele);if (kvp.Value.Count == 0){events.RemoveAndDispose(deleType);}}}/// <summary>/// 返回同类型可空委托列表,禁止长期持有返回值的引用,该引用在对象池回收后无效,且可能存在元素的数量改变/// </summary>/// <param name="deleType"></param>/// <returns></returns>public List<Delegate> GetDelegateList(Type deleType){return events.GetValueOrDefault(deleType)?.Value;} }
}
五、派发器
派发器负责 具体对象到事件组的获取
/********************************************************************************************* Author : yueh0607* Date : 2023.1.30* Description : * 抽象模型消息派发器,负责处理每类消息的派发任务,可以指定接收者与派发类型*/using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;namespace AirFramework
{/// <summary>/// 消息分发器/// </summary>public partial class MessageDispatcher : Unit{public int Count => events.Count;private Dictionary<IMessageReceiver, UnitDelegateGroup> events = new();internal Dictionary<IMessageReceiver,UnitDelegateGroup> EevensList=> events;}/// <summary>/// 消息分发器/// </summary>public partial class MessageDispatcher : Unit{//从对象池获取internal static Func<UnitDelegateGroup> GetGroupFromPool = () => Framework.Pool.Allocate<UnitDelegateGroup>();/// <summary>/// 访问或添加:获取指定接收者的委托派发组/// </summary>/// <param name="receiver"></param>/// <returns></returns>public UnitDelegateGroup GetOrAddGroup(IMessageReceiver receiver){return events.GetValueOrAddDefault(receiver, GetGroupFromPool);}/// <summary>/// 添加:为接收者添加指定的委托派发/// </summary>public void Add(IMessageReceiver receiver, Type deleType, Delegate dele){events.GetValueOrAddDefault(receiver, GetGroupFromPool).Value.Add(dele, deleType);}/// <summary>/// 移除:为接收者移除指定委托派发/// </summary>public void Remove(IMessageReceiver receiver, Type deleType, Delegate dele){if (events.TryGetValue(receiver, out var group)){group.Value.Remove(dele, deleType);if (group.Value.Count == 0){events.RemoveAndDispose(receiver);}}}/// <summary>/// 移除:为接收者移除全部委托派发/// </summary>public void Remove(IMessageReceiver receiver){events.TryRemoveAndDispose(receiver);}}
}
六、管理器
using System;
using System.Collections.Generic;
using System.Linq;namespace AirFramework
{/// <summary>/// 消息派发管理器/// </summary>public partial class MessageManager : GlobalManager, IMessageReceiver{/// <summary>/// 消息派发器存储器/// </summary>private Dictionary<Type, UnitMessageDispatcher> dispatchers = new();/// <summary>/// 消息移除:移除全局所有的该类消息/// </summary>public void Remove<T>() where T : IMessage{UnRegister(typeof(T));}/// <summary>/// 消息移除:移除该对象所有的该类消息/// </summary>public void Remove<T>(IMessageReceiver receiver) where T : IMessage{UnRegister(typeof(T), receiver);}}
}
using System;
using System.Collections.Generic;
using System.Linq;namespace AirFramework
{/// <summary>/// 消息派发管理器/// </summary>public partial class MessageManager : GlobalManager, IMessageReceiver{internal static Func<UnitMessageDispatcher> GetDispatcherFromPool = () => Framework.Pool.Allocate<UnitMessageDispatcher>();/// <summary>/// 基础消息注册/// </summary>internal void Register(Type messageType, IMessageReceiver receiver, Type deleType, Delegate message){dispatchers.GetValueOrAddDefault(messageType,GetDispatcherFromPool).Value.Add(receiver, deleType, message);}/// <summary>/// 基础消息移除/// </summary>internal void UnRegister(Type messageType, IMessageReceiver receiver, Type deleType, Delegate message){if(dispatchers.TryGetValue(messageType,out var dispatcher)){dispatcher.Value.Remove(receiver, deleType, message);if (dispatcher.Value.Count == 0){dispatchers.RemoveAndDispose(messageType);}}}/// <summary>/// 基础消息移除:移除接收者的全部该类型消息/// </summary>internal void UnRegister(Type messageType, IMessageReceiver receiver){if (dispatchers.TryGetValue(messageType, out var dispatcher)){dispatcher.Value.Remove(receiver);if (dispatcher.Value.Count == 0){dispatchers.RemoveAndDispose(messageType);}}}/// <summary>/// 基础消息移除:移除全部该类型消息/// </summary>/// <param name="messageType"></param>internal void UnRegister(Type messageType){dispatchers.TryRemoveAndDispose(messageType);}/// <summary>/// 基础消息移除:移除全局所有消息/// </summary>internal void UnRegister(){dispatchers.ClearAndDispose();}/// <summary>/// 基础消息移除:移除对象身上全部消息/// </summary>/// <param name="receiver"></param>internal void UnRegister(IMessageReceiver receiver){var queue = Framework.Pool.Allocate<UnitQueue<Type>>();foreach(var dispatcher in dispatchers){dispatcher.Value.Value.Remove(receiver);if (dispatcher.Value.Value.Count == 0){queue.Value.Enqueue(dispatcher.Key);}}while(queue.Value.Count > 0) dispatchers.Remove(queue.Value.Dequeue());queue.Dispose();} }
}
七、拓展方法
我们实现了基本的注册和取消注册,还有获取,但没有发布,通过添加拓展的方式,我们就实现了上文提到的语法
public static UnitDelegateGroup Operator<MessageType>(this IMessageReceiver receiver) where MessageType : IMessage{return Framework.Message.Operator<MessageType>(receiver);}/// 发布public static void Publish(this UnitDelegateGroup container){var events = container?.Value.Get<Action>();if (events == null ||events.Count==0) return;for(int i=0; i<events.Count; i++){(events[i] as Action)?.Invoke();}}/// <summary>/// 发布/// </summary>public static void Publish<T1>(this UnitDelegateGroup container, T1 arg1){var events = container?.Value.Get<Action<T1>>();if (events == null || events.Count == 0) return;for (int i = 0; i < events.Count; i++){(events[i] as Action<T1>)?.Invoke(arg1);}}public static void Subscribe(this UnitDelegateGroup container,Action message)=>container?.Value.Add<Action>(message);
public static void UnSubscribe(this UnitDelegateGroup container, Action message)=> container?.Value.Remove<Action>(message);
八、 带返回值的问答
我们有时需要消息带有返回值,在上文基础上我可以这样调用
public static void Response<T1>(this UnitDelegateGroup container, Func<T1> message)=>container?.Value.Add<Func<T1>>(message);
public static void Cancel<T1>(this UnitDelegateGroup container, Func<T1> message)=> container?.Value.Remove<Func<T1>>(message);public static bool TryCall<T1>(this UnitDelegateGroup container,out T1 result){var events = container?.Value.Get<Func<T1>>();if (events == null || events.Count == 0){result = default;return false;}result= (events[0] as Func<T1>).Invoke();return true;}
九、使用方式
Framework.Message.Operator<IAwake>().Subscribe<int,int,float>(myMethod);//注册管理器消息
Framework.Message.Operator<IAwake>().Publish(1, 2, 100.0f);//管理器消息
Framework.Message.Dispatcher<IAwake>().Publish(1,2,100.0f);//全局消息包含包含管理器
我们甚至可以加一下拓展,实现this.Operator的简化语法
本文就到这里,至此消息机制系列结束,作者现在大一填完高一时的坑了。