中介者模式
大约 12 分钟约 3641 字
中介者模式
简介
中介者(Mediator)用一个中介对象封装一系列对象的交互,使各对象不需要显式引用彼此。理解中介者模式,有助于降低多个组件之间的直接耦合。在大型系统中,当多个对象之间存在复杂的引用关系时,中介者模式能够将这些混乱的多对多关系简化为清晰的一对多关系——所有组件只与中介者通信,由中介者负责协调和转发消息。
特点
UML 结构
┌──────────────────┐ ┌──────────────────┐
│ Colleague │ │ Mediator │
│──────────────────│ │──────────────────│
│ -mediator │◄────────│ +notify() │
│ +send() │ │ +register() │
│ +receive() │ │ +broadcast() │
└──────────────────┘ └──────────────────┘
△ △
│ │
┌───────┴───────┐ ┌────────┴────────┐
│ ConcreteColleague │ │ ConcreteMediator │
│──────────────────│ │─────────────────│
│ +send() │ │ -colleagues[] │
│ +receive() │ │ +notify() │
└──────────────────┘ │ +register() │
└─────────────────┘实现
聊天室中介者
// 中介者接口
public interface IChatMediator
{
void Register(User user);
void Unregister(string userName);
void SendMessage(string from, string to, string message);
void Broadcast(string from, string message);
}
// 组件 — 用户
public class User
{
public string Name { get; }
private readonly IChatMediator _mediator;
public User(string name, IChatMediator mediator)
{
Name = name; _mediator = mediator;
_mediator.Register(this);
}
public void Send(string to, string message) => _mediator.SendMessage(Name, to, message);
public void Broadcast(string message) => _mediator.Broadcast(Name, message);
public void Receive(string from, string message) =>
Console.WriteLine($"[{Name}] 收到来自 {from} 的消息: {message}");
}
// 具体中介者
public class ChatRoom : IChatMediator
{
private readonly Dictionary<string, User> _users = new();
public void Register(User user) => _users[user.Name] = user;
public void Unregister(string userName) => _users.Remove(userName);
public void SendMessage(string from, string to, string message)
{
if (_users.TryGetValue(to, out var user))
user.Receive(from, message);
else
Console.WriteLine($"[系统] 用户 {to} 不在线");
}
public void Broadcast(string from, string message)
{
foreach (var user in _users.Values.Where(u => u.Name != from))
user.Receive(from, message);
}
}
// 使用
var chatRoom = new ChatRoom();
var alice = new User("Alice", chatRoom);
var bob = new User("Bob", chatRoom);
var charlie = new User("Charlie", chatRoom);
alice.Send("Bob", "你好 Bob");
bob.Broadcast("大家好!");UI 组件中介者
public interface IDialogMediator
{
void Notify(object sender, string event_);
}
public class LoginDialog : IDialogMediator
{
public TextBox UsernameBox { get; } = new();
public TextBox PasswordBox { get; } = new();
public CheckBox RememberCheck { get; } = new();
public Button LoginButton { get; } = new();
public Label ErrorLabel { get; } = new();
public LoginDialog()
{
UsernameBox.SetMediator(this);
PasswordBox.SetMediator(this);
RememberCheck.SetMediator(this);
}
public void Notify(object sender, string event_)
{
if (sender == UsernameBox || sender == PasswordBox)
{
var valid = !string.IsNullOrEmpty(UsernameBox.Text) && !string.IsNullOrEmpty(PasswordBox.Text);
LoginButton.IsEnabled = valid;
if (valid) ErrorLabel.Text = "";
}
if (sender == UsernameBox && event_ == "TextChanged" && UsernameBox.Text.Contains(" "))
ErrorLabel.Text = "用户名不能包含空格";
}
}
public class TextBox
{
private IDialogMediator? _mediator;
public string Text { get; set; } = "";
public void SetMediator(IDialogMediator mediator) => _mediator = mediator;
public void Input(string text) { Text = text; _mediator?.Notify(this, "TextChanged"); }
}
public class CheckBox { private IDialogMediator? _mediator; public bool IsChecked { get; set; }
public void SetMediator(IDialogMediator mediator) => _mediator = mediator; }
public class Button { public bool IsEnabled { get; set; } }
public class Label { public string Text { get; set; } = ""; }MediatR 中介者框架
// ASP.NET Core 中使用 MediatR
public class CreateOrderCommand : IRequest<OrderResult>
{
public string CustomerId { get; init; } = "";
public List<OrderItemDto> Items { get; init; } = new();
}
public record OrderResult(Guid OrderId, decimal Total);
public record OrderItemDto(string ProductId, int Quantity, decimal Price);
// Handler — 相当于中介者中的具体处理逻辑
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly INotificationService _notification;
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
// 中介者协调多个服务
await _inventory.ReserveAsync(request.Items, ct);
var total = request.Items.Sum(i => i.Quantity * i.Price);
await _payment.PreAuthorizeAsync(request.CustomerId, total, ct);
await _notification.SendConfirmationAsync(request.CustomerId, ct);
return new OrderResult(Guid.NewGuid(), total);
}
}
// 注册
services.AddMediatR(typeof(CreateOrderHandler));航空塔台调度(真实场景)
// 中介者 — 塔台
public interface IAirTrafficControl
{
void RegisterAircraft(Aircraft aircraft);
void RequestLanding(Aircraft aircraft);
void RequestTakeoff(Aircraft aircraft);
void ReportLanded(Aircraft aircraft);
}
public class ControlTower : IAirTrafficControl
{
private readonly Queue<Aircraft> _landingQueue = new();
private readonly Queue<Aircraft> _takeoffQueue = new();
private readonly HashSet<Aircraft> _aircraft = new();
private bool _runwayBusy;
public void RegisterAircraft(Aircraft aircraft) => _aircraft.Add(aircraft);
public void RequestLanding(Aircraft aircraft)
{
if (_runwayBusy)
{
_landingQueue.Enqueue(aircraft);
Console.WriteLine($"[塔台] {aircraft.CallSign} 进入降落等待队列");
return;
}
_runwayBusy = true;
Console.WriteLine($"[塔台] {aircraft.CallSign} 允许降落");
}
public void RequestTakeoff(Aircraft aircraft)
{
if (_runwayBusy)
{
_takeoffQueue.Enqueue(aircraft);
Console.WriteLine($"[塔台] {aircraft.CallSign} 进入起飞等待队列");
return;
}
_runwayBusy = true;
Console.WriteLine($"[塔台] {aircraft.CallSign} 允许起飞");
}
public void ReportLanded(Aircraft aircraft)
{
_runwayBusy = false;
if (_takeoffQueue.Count > 0)
{
var next = _takeoffQueue.Dequeue();
_runwayBusy = true;
Console.WriteLine($"[塔台] {next.CallSign} 允许起飞");
}
else if (_landingQueue.Count > 0)
{
var next = _landingQueue.Dequeue();
_runwayBusy = true;
Console.WriteLine($"[塔台] {next.CallSign} 允许降落");
}
}
}
// 同事类 — 飞机
public class Aircraft
{
public string CallSign { get; }
private readonly IAirTrafficControl _tower;
public Aircraft(string callSign, IAirTrafficControl tower)
{
CallSign = callSign;
_tower = tower;
_tower.RegisterAircraft(this);
}
public void Land()
{
_tower.RequestLanding(this);
Console.WriteLine($"{CallSign} 正在降落...");
_tower.ReportLanded(this);
}
public void Takeoff()
{
_tower.RequestTakeoff(this);
Console.WriteLine($"{CallSign} 正在起飞...");
_tower.ReportLanded(this);
}
}
// 使用 — 多架飞机互不引用,全部通过塔台协调
var tower = new ControlTower();
var flight1 = new Aircraft("CA1234", tower);
var flight2 = new Aircraft("MU5678", tower);
var flight3 = new Aircraft("CZ9012", tower);
flight1.Land();
flight2.Land(); // 进入等待队列
flight3.Takeoff(); // 进入等待队列泛型事件中介者
// 基于事件的泛型中介者 — 支持强类型消息
public interface IMediator
{
Task<TResponse> Send<TResponse>(IRequest<TResponse> request);
Task Publish<TNotification>(TNotification notification) where TNotification : INotification;
}
public interface IRequest<TResponse> { }
public interface INotification { }
// 泛型中介者实现
public class EventMediator : IMediator
{
private readonly IServiceProvider _serviceProvider;
public EventMediator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<TResponse> Send<TResponse>(IRequest<TResponse> request)
{
var handlerType = typeof(IRequestHandler<,>)
.MakeGenericType(request.GetType(), typeof(TResponse));
var handler = _serviceProvider.GetService(handlerType)
?? throw new InvalidOperationException($"未注册 Handler: {handlerType.Name}");
var method = handlerType.GetMethod("Handle");
return await (Task<TResponse>)method!.Invoke(handler, new object[] { request, CancellationToken.None })!;
}
public async Task Publish<TNotification>(TNotification notification) where TNotification : INotification
{
var handlerType = typeof(INotificationHandler<>).MakeGenericType(typeof(TNotification));
var handlers = _serviceProvider.GetServices(handlerType);
foreach (var handler in handlers)
{
var method = handlerType.GetMethod("Handle");
await (Task)method!.Invoke(handler, new object[] { notification, CancellationToken.None })!;
}
}
}
// Handler 接口
public interface IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(TRequest request, CancellationToken ct);
}
public interface INotificationHandler<TNotification> where TNotification : INotification
{
Task Handle(TNotification notification, CancellationToken ct);
}中介者 vs 观察者模式
| 维度 | 中介者模式 | 观察者模式 |
|---|---|---|
| 目的 | 协调多个同事对象间的交互 | 一对多的依赖通知 |
| 关系 | 多对多通过中介者变为一对多 | 一对多的广播 |
| 耦合 | 同事只知道中介者 | 主题不知道观察者 |
| 控制流 | 中介者主动协调 | 观察者被动接收 |
| 适用场景 | 组件间需要复杂协调 | 状态变更通知 |
中介者 vs 外观模式
| 维度 | 中介者模式 | 外观模式 |
|---|---|---|
| 目的 | 解耦多个组件之间的交互 | 简化复杂子系统的接口 |
| 关注点 | 组件之间的通信 | 子系统的使用便捷性 |
| 方向 | 双向通信 | 单向(客户端到子系统) |
| 知晓范围 | 中介者知道所有同事 | 外观知道子系统,反之不然 |
生产环境注意事项
1. 防止中介者变成上帝对象
// 错误做法 — 所有交互都堆积在一个中介者中
public class GodMediator
{
public void HandleUserAction() { /* ... */ }
public void HandleOrderAction() { /* ... */ }
public void HandlePaymentAction() { /* ... */ }
public void HandleInventoryAction() { /* ... */ }
// 越来越臃肿...
}
// 正确做法 — 按领域拆分中介者
public class OrderMediator { /* 处理订单相关协调 */ }
public class PaymentMediator { /* 处理支付相关协调 */ }
public class NotificationMediator { /* 处理通知相关协调 */ }2. 线程安全考虑
public class ThreadSafeChatMediator : IChatMediator
{
private readonly ConcurrentDictionary<string, User> _users = new();
private readonly object _lock = new();
public void Broadcast(string from, string message)
{
foreach (var user in _users.Values.Where(u => u.Name != from).ToList())
{
Task.Run(() => user.Receive(from, message));
}
}
}3. 中介者与依赖注入的配合
// 将中介者注册为单例或作用域服务
services.AddSingleton<IChatMediator, ChatRoom>();
// 通过 DI 解耦组件
public class User
{
private readonly IChatMediator _mediator;
public User(string name, IChatMediator mediator)
{
Name = name;
_mediator = mediator;
_mediator.Register(this);
}
}优点
缺点
总结
中介者模式通过中心协调者解耦多个组件。适用于 UI 对话框(表单联动)、聊天系统、飞机塔台调度等场景。MediatR 是 .NET 生态中流行的中介者框架,用于 CQRS 的命令/查询分发。注意避免中介者变成上帝对象,可按功能拆分为多个中介者。建议在组件间有复杂交互、需要降低直接耦合时使用。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
- 中介者模式的本质是将多对多的交互关系转化为一对多,降低系统整体的耦合度。
- 中介者不应该是所有逻辑的汇聚点,应按职责拆分。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
- 在引入中介者前,先评估组件间的交互复杂度是否真的需要中介者。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
- 将所有逻辑都塞进中介者,导致上帝对象。
- 混淆中介者与观察者模式的适用场景。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
- 学习 MediatR、MassTransit 等框架的底层设计,理解中介者模式的工程化实现。
适用场景
- 当你准备把《中介者模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
- 典型场景:UI 表单联动、聊天室、航空调度、CQRS 命令分发。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
- 按业务领域拆分中介者,避免单个中介者承载过多职责。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
- 检查中介者是否变成了上帝对象,是否需要按领域拆分。
复盘问题
- 如果把《中介者模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《中介者模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《中介者模式》最大的收益和代价分别是什么?
- 你的中介者是否按领域拆分?如果所有交互集中在一个类中,如何重构?
4. 中介者与事件总线的结合
// 将中介者与事件总线结合,实现跨模块通信
public interface IEventBus
{
void Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : IEvent;
void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : IEvent;
Task PublishAsync<TEvent>(TEvent @event, CancellationToken ct = default) where TEvent : IEvent;
}
public interface IEvent { }
public interface IEventHandler<TEvent> where TEvent : IEvent
{
Task HandleAsync(TEvent @event, CancellationToken ct);
}
public class InMemoryEventBus : IEventBus
{
private readonly ConcurrentDictionary<Type, List<object>> _handlers = new();
private readonly ILogger<InMemoryEventBus> _logger;
public InMemoryEventBus(ILogger<InMemoryEventBus> logger) => _logger = logger;
public void Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : IEvent
{
var handlers = _handlers.GetOrAdd(typeof(TEvent), _ => new List<object>());
lock (handlers) { handlers.Add(handler); }
}
public void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : IEvent
{
if (_handlers.TryGetValue(typeof(TEvent), out var handlers))
lock (handlers) { handlers.Remove(handler); }
}
public async Task PublishAsync<TEvent>(TEvent @event, CancellationToken ct) where TEvent : IEvent
{
if (!_handlers.TryGetValue(typeof(TEvent), out var handlers)) return;
List<object> snapshot;
lock (handlers) { snapshot = handlers.ToList(); }
foreach (var handler in snapshot)
{
try
{
await ((IEventHandler<TEvent>)handler).HandleAsync(@event, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "事件处理器执行失败: {EventType}", typeof(TEvent).Name);
}
}
}
}5. 中介者与命令模式的结合
// 将中介者与命令模式结合,实现可撤销的操作
public interface ICommand
{
void Execute();
void Undo();
}
public class CommandMediator
{
private readonly Stack<ICommand> _undoStack = new();
private readonly Stack<ICommand> _redoStack = new();
private readonly Dictionary<string, Func<object[], ICommand>> _commandFactory = new();
public void RegisterCommand(string commandName, Func<object[], ICommand> factory)
{
_commandFactory[commandName] = factory;
}
public void Execute(string commandName, params object[] args)
{
if (!_commandFactory.TryGetValue(commandName, out var factory))
throw new InvalidOperationException($"未注册命令: {commandName}");
var command = factory(args);
command.Execute();
_undoStack.Push(command);
_redoStack.Clear(); // 新操作清空重做栈
}
public void Undo()
{
if (_undoStack.Count == 0) return;
var command = _undoStack.Pop();
command.Undo();
_redoStack.Push(command);
}
public void Redo()
{
if (_redoStack.Count == 0) return;
var command = _redoStack.Pop();
command.Execute();
_undoStack.Push(command);
}
}
// 使用
var mediator = new CommandMediator();
mediator.RegisterCommand("Move", args => new MoveCommand((IWidget)args[0], (int)args[1], (int)args[2]));
mediator.Execute("Move", widget, 100, 200);
mediator.Undo();
mediator.Redo();中介者模式的测试策略
单元测试中介者
// 测试中介者模式 — 验证消息传递的正确性
[TestClass]
public class ChatRoomTests
{
[TestMethod]
public void SendMessage_ToSpecificUser_ReceiverGetsMessage()
{
// Arrange
var chatRoom = new ChatRoom();
var alice = new User("Alice", chatRoom);
var bob = new User("Bob", chatRoom);
// 使用 StringWriter 捕获控制台输出
using var sw = new StringWriter();
Console.SetOut(sw);
// Act
alice.Send("Bob", "Hello Bob");
// Assert
var output = sw.ToString();
Assert.IsTrue(output.Contains("[Bob] 收到来自 Alice 的消息: Hello Bob"));
}
[TestMethod]
public void Broadcast_AllOtherUsersReceiveMessage()
{
var chatRoom = new ChatRoom();
var alice = new User("Alice", chatRoom);
var bob = new User("Bob", chatRoom);
var charlie = new User("Charlie", chatRoom);
using var sw = new StringWriter();
Console.SetOut(sw);
alice.Broadcast("Hello everyone");
var output = sw.ToString();
Assert.IsTrue(output.Contains("[Bob]"));
Assert.IsTrue(output.Contains("[Charlie]"));
Assert.IsFalse(output.Contains("[Alice] 收到")); // 发送者不应收到
}
[TestMethod]
public void SendMessage_ToNonExistentUser_ShowsSystemMessage()
{
var chatRoom = new ChatRoom();
var alice = new User("Alice", chatRoom);
using var sw = new StringWriter();
Console.SetOut(sw);
alice.Send("Nobody", "Hello");
var output = sw.ToString();
Assert.IsTrue(output.Contains("用户 Nobody 不在线"));
}
}Mock 中介者进行组件测试
// 使用 Mock 测试同事类,无需真实中介者
[TestClass]
public class AircraftTests
{
[TestMethod]
public void Aircraft_RegistersWithTower_OnCreation()
{
var mockTower = new Mock<IAirTrafficControl>();
var aircraft = new Aircraft("TEST001", mockTower.Object);
mockTower.Verify(t => t.RegisterAircraft(aircraft), Times.Once);
}
[TestMethod]
public void Aircraft_RequestsLanding_WhenLandCalled()
{
var mockTower = new Mock<IAirTrafficControl>();
var aircraft = new Aircraft("TEST001", mockTower.Object);
aircraft.Land();
mockTower.Verify(t => t.RequestLanding(aircraft), Times.Once);
}
}中介者在微服务架构中的应用
API 网关中介者
// API 网关作为微服务间的中介者
public class ApiGatewayMediator
{
private readonly Dictionary<string, IServiceClient> _services = new();
private readonly ILogger<ApiGatewayMediator> _logger;
public ApiGatewayMediator(ILogger<ApiGatewayMediator> logger) => _logger = logger;
public void RegisterService(string serviceName, IServiceClient client)
{
_services[serviceName] = client;
}
public async Task<TResponse> ForwardAsync<TResponse>(string serviceName, string path, object request, CancellationToken ct)
{
if (!_services.TryGetValue(serviceName, out var client))
throw new InvalidOperationException($"服务 {serviceName} 未注册");
_logger.LogInformation("转发请求到 {Service}: {Path}", serviceName, path);
var stopwatch = Stopwatch.StartNew();
try
{
var response = await client.SendAsync<TResponse>(path, request, ct);
stopwatch.Stop();
_logger.LogInformation("请求完成: {Service} ({Ms}ms)", serviceName, stopwatch.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "请求失败: {Service} ({Ms}ms)", serviceName, stopwatch.ElapsedMilliseconds);
throw;
}
}
}
public interface IServiceClient
{
Task<TResponse> SendAsync<TResponse>(string path, object request, CancellationToken ct);
}