发布订阅模式是一种消息通信模式,发送者也就是发布者不会直接将消息发送给特定的接收者也就是订阅者,而是通过事件聚合器作为中间层进行消息转发,实现发布者和订阅者的解耦。这种模式在模块化开发、跨组件通信场景中应用非常广泛。

发布订阅模式的核心概念
发布订阅模式主要包含三个核心角色:
- 发布者:负责产生消息,将消息发送到事件聚合器中,不需要知道有哪些订阅者会接收消息。
- 订阅者:负责定义消息处理逻辑,向事件聚合器注册自己感兴趣的消息类型,当对应消息发布时自动触发处理逻辑。
- 事件聚合器:作为中间枢纽,管理所有的订阅关系,接收发布者发送的消息并转发给对应的订阅者。
基础事件聚合器实现
我们可以先实现一个简单的泛型事件聚合器,支持任意类型的消息发布和订阅。核心思路是用一个字典存储消息类型和对应的处理委托列表。
using System;
using System.Collections.Generic;
// 事件聚合器接口
public interface IEventAggregator
{
// 订阅指定类型的消息
void Subscribe<TMessage>(Action<TMessage> handler);
// 取消订阅指定类型的消息
void Unsubscribe<TMessage>(Action<TMessage> handler);
// 发布指定类型的消息
void Publish<TMessage>(TMessage message);
}
// 基础事件聚合器实现
public class EventAggregator : IEventAggregator
{
// 存储消息类型和对应的处理委托列表
private readonly Dictionary<Type, List<Delegate>> _handlers = new Dictionary<Type, List<Delegate>>();
public void Subscribe<TMessage>(Action<TMessage> handler)
{
var messageType = typeof(TMessage);
// 如果不存在该消息类型的处理列表,先创建
if (!_handlers.ContainsKey(messageType))
{
_handlers[messageType] = new List<Delegate>();
}
// 添加处理委托
_handlers[messageType].Add(handler);
}
public void Unsubscribe<TMessage>(Action<TMessage> handler)
{
var messageType = typeof(TMessage);
if (_handlers.ContainsKey(messageType))
{
_handlers[messageType].Remove(handler);
// 如果处理列表为空,移除该消息类型的记录
if (_handlers[messageType].Count == 0)
{
_handlers.Remove(messageType);
}
}
}
public void Publish<TMessage>(TMessage message)
{
var messageType = typeof(TMessage);
if (_handlers.ContainsKey(messageType))
{
// 遍历所有处理委托并执行
foreach (var handler in _handlers[messageType])
{
// 将委托转换为对应类型的Action执行
((Action<TMessage>)handler).Invoke(message);
}
}
}
}
使用示例
我们定义两个消息类型,模拟发布者和订阅者的交互过程。
// 定义消息类型1:用户登录消息
public class UserLoginMessage
{
public string UserName { get; set; }
public DateTime LoginTime { get; set; }
}
// 定义消息类型2:订单创建消息
public class OrderCreatedMessage
{
public int OrderId { get; set; }
public decimal Amount { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 创建事件聚合器实例
IEventAggregator eventAggregator = new EventAggregator();
// 订阅者1:处理用户登录消息
eventAggregator.Subscribe<UserLoginMessage>(msg =>
{
Console.WriteLine($"用户登录通知:用户{msg.UserName}于{msg.LoginTime}登录系统");
});
// 订阅者2:处理订单创建消息
eventAggregator.Subscribe<OrderCreatedMessage>(msg =>
{
Console.WriteLine($"订单创建通知:订单{msg.OrderId}已创建,金额{msg.Amount}元");
});
// 发布者发布用户登录消息
eventAggregator.Publish(new UserLoginMessage
{
UserName = "张三",
LoginTime = DateTime.Now
});
// 发布者发布订单创建消息
eventAggregator.Publish(new OrderCreatedMessage
{
OrderId = 1001,
Amount = 299.9m
});
// 取消用户登录消息的订阅
Action<UserLoginMessage> loginHandler = msg =>
{
Console.WriteLine($"用户登录通知:用户{msg.UserName}于{msg.LoginTime}登录系统");
};
eventAggregator.Unsubscribe(loginHandler);
// 再次发布用户登录消息,此时没有订阅者会处理
eventAggregator.Publish(new UserLoginMessage
{
UserName = "李四",
LoginTime = DateTime.Now
});
}
}
高级优化点
上述基础实现可以满足简单场景的需求,在实际高级应用中还可以做以下优化:
- 支持弱引用委托,避免订阅者被事件聚合器强引用导致无法被垃圾回收,解决内存泄漏问题。
- 支持异步消息处理,允许订阅者的处理逻辑是异步方法,事件聚合器可以异步转发消息。
- 增加消息过滤功能,订阅者可以指定过滤条件,只有满足条件的消息才会被处理。
- 支持消息优先级,高优先级的消息可以先被处理,或者按照优先级顺序转发给订阅者。
注意事项
在使用C#实现发布订阅模式时需要注意以下几点:
- 消息类型尽量使用不可变类型,避免消息在传递过程中被修改导致不可预期的问题。
- 订阅者处理逻辑尽量保持轻量,不要做耗时操作,避免阻塞消息发布线程。
- 及时取消不需要的订阅,避免无效的处理逻辑执行,也减少内存占用。
- 事件聚合器如果是单例模式,需要注意线程安全问题,上述基础实现没有做线程安全处理,多线程场景下需要加锁或者使用线程安全的集合。
发布订阅模式的核心是解耦,不要为了使用模式而使用模式,如果组件之间本身耦合度很低,直接调用反而更清晰,不需要强行引入事件聚合器。