如何解除依赖呢?其实答案就在本文的两张图上,仔细对比我们可以很直观的看到,Event Bus就相当于一个介于Publisher和Subscriber中间的桥梁。它隔离了Publlisher和Subscriber之间的直接依赖,接管了所有事件的发布和订阅逻辑,并负责事件的中转。
Event Bus终于要粉墨登场了!!!
分析一下,如果EventBus要接管所有事件的发布和订阅,那它则需要有一个容器来记录事件源和事件处理。那又如何触发呢?有了事件源,我们就自然能找到绑定的事件处理逻辑,通过反射触发。代码如下:
/// <summary>
/// 事件总线
/// </summary>
public class EventBus
{
public static EventBus Default => new EventBus();
/// <summary>
/// 定义线程安全集合
/// </summary>
private readonly ConcurrentDictionary<Type, List<Type>> _eventAndHandlerMapping;
public EventBus()
{
_eventAndHandlerMapping = new ConcurrentDictionary<Type, List<Type>>();
MapEventToHandler();
}
/// <summary>
///通过反射,将事件源与事件处理绑定
/// </summary>
private void MapEventToHandler()
{
Assembly assembly = Assembly.GetEntryAssembly();
foreach (var type in assembly.GetTypes())
{
if (typeof(IEventHandler).IsAssignableFrom(type))//判断当前类型是否实现了IEventHandler接口
{
Type handlerInterface = type.GetInterface("IEventHandler`1");//获取该类实现的泛型接口
if (handlerInterface != null)
{
Type eventDataType = handlerInterface.GetGenericArguments()[0]; // 获取泛型接口指定的参数类型
if (_eventAndHandlerMapping.ContainsKey(eventDataType))
{
List<Type> handlerTypes = _eventAndHandlerMapping[eventDataType];
handlerTypes.Add(type);
_eventAndHandlerMapping[eventDataType] = handlerTypes;
}
else
{
var handlerTypes = new List<Type> { type };
_eventAndHandlerMapping[eventDataType] = handlerTypes;
}
}
}
}
}
/// <summary>
/// 手动绑定事件源与事件处理
/// </summary>
/// <typeparam name="TEventData"></typeparam>
/// <param name="eventHandler"></param>
public void Register<TEventData>(Type eventHandler)
{
List<Type> handlerTypes = _eventAndHandlerMapping[typeof(TEventData)];
if (!handlerTypes.Contains(eventHandler))
{
handlerTypes.Add(eventHandler);
_eventAndHandlerMapping[typeof(TEventData)] = handlerTypes;
}
}
/// <summary>
/// 手动解除事件源与事件处理的绑定
/// </summary>
/// <typeparam name="TEventData"></typeparam>
/// <param name="eventHandler"></param>
public void UnRegister<TEventData>(Type eventHandler)
{
List<Type> handlerTypes = _eventAndHandlerMapping[typeof(TEventData)];
if (handlerTypes.Contains(eventHandler))
{
handlerTypes.Remove(eventHandler);
_eventAndHandlerMapping[typeof(TEventData)] = handlerTypes;
}
}
/// <summary>
/// 根据事件源触发绑定的事件处理
/// </summary>
/// <typeparam name="TEventData"></typeparam>
/// <param name="eventData"></param>
public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
{
List<Type> handlers = _eventAndHandlerMapping[eventData.GetType()];
if (handlers != null && handlers.Count > 0)
{
foreach (var handler in handlers)
{
MethodInfo methodInfo = handler.GetMethod("HandleEvent");
if (methodInfo != null)
{
object obj = Activator.CreateInstance(handler);
methodInfo.Invoke(obj, new object[] { eventData });
}
}
}
}
}
事件总线主要定义三个方法,注册、取消注册、事件触发。还有一点就是我们在构造函数中通过反射去进行事件源和事件处理的绑定。
代码注释已经很清楚了,这里就不过多解释了。
下面我们就来修改Demo,修改FishingRod的事件触发:
/// <summary>
/// 下钩
/// </summary>
public void ThrowHook(FishingMan man)
{
Console.WriteLine("开始下钩!");
//用随机数模拟鱼咬钩,若随机数为偶数,则为鱼咬钩
if (new Random().Next() % 2 == 0)
{
var a = new Random(10).Next();
var type = (FishType)new Random().Next(0, 5);
Console.WriteLine("铃铛:叮叮叮,鱼儿咬钩了");
if (FishingEvent != null)
{
var eventData = new FishingEventData() { FishType = type, FishingMan = man };
//FishingEvent(eventData);//不再需要通过事件委托触发
EventBus.Default.Trigger<FishingEventData>(eventData);//直接通过事件总线触发即可
}
}
}
至此,事件总线的雏形已经形成!
5.事件总线的总结
通过上面一步一步的分析和实践,发现事件总线也不是什么高深的概念,只要我们自己善于思考,勤于动手,也能实现自己的事件总线。
根据我们的实现,大概总结出以下几条:
最后,以上事件总线的实现只是一个雏形,还有很多潜在的问题。有兴趣的不妨思考完善一下,我也会继续更新完善,尽情期待!
参考资料
ABP EventBus
DDD~领域事件与事件总线
DDD事件总线的实现
posted @