命令模式详解
大约 8 分钟约 2356 字
命令模式详解
简介
命令模式(Command Pattern)是一种行为型设计模式,它将请求(或操作)封装为独立的对象,从而允许使用队列、请求和操作来参数化其他对象,并支持撤销操作、操作记录和事务等功能。在 C# 中,命令模式广泛应用于任务队列、撤销/重做机制,以及通过 MediatR 库实现的 CQRS 架构中的 Command 对象。
特点
基础命令模式实现
智能家居控制示例
// 命令接口
public interface ICommand
{
void Execute();
void Undo();
string Description { get; }
}
// 接收者 - 灯
public class Light
{
public string Location { get; }
public bool IsOn { get; private set; }
public Light(string location) => Location = location;
public void TurnOn()
{
IsOn = true;
Console.WriteLine($"[灯] {Location} 的灯已打开");
}
public void TurnOff()
{
IsOn = false;
Console.WriteLine($"[灯] {Location} 的灯已关闭");
}
}
// 接收者 - 空调
public class AirConditioner
{
public int Temperature { get; private set; } = 26;
public void SetTemperature(int temp)
{
var old = Temperature;
Temperature = temp;
Console.WriteLine($"[空调] 温度从 {old}°C 调整到 {temp}°C");
}
}
// 具体命令 - 开灯命令
public class LightOnCommand : ICommand
{
private readonly Light _light;
public LightOnCommand(Light light) => _light = light;
public string Description => $"打开 {_light.Location} 的灯";
public void Execute() => _light.TurnOn();
public void Undo() => _light.TurnOff();
}
// 具体命令 - 关灯命令
public class LightOffCommand : ICommand
{
private readonly Light _light;
public LightOffCommand(Light light) => _light = light;
public string Description => $"关闭 {_light.Location} 的灯";
public void Execute() => _light.TurnOff();
public void Undo() => _light.TurnOn();
}
// 具体命令 - 空调调温命令
public class SetTemperatureCommand : ICommand
{
private readonly AirConditioner _ac;
private readonly int _newTemp;
private int _previousTemp;
public SetTemperatureCommand(AirConditioner ac, int temperature)
{
_ac = ac;
_newTemp = temperature;
}
public string Description => $"设置空调温度为 {_newTemp}°C";
public void Execute()
{
_previousTemp = _ac.Temperature;
_ac.SetTemperature(_newTemp);
}
public void Undo() => _ac.SetTemperature(_previousTemp);
}
// 宏命令 - 一次执行多个命令
public class MacroCommand : ICommand
{
private readonly List<ICommand> _commands = new();
public MacroCommand(IEnumerable<ICommand> commands) => _commands.AddRange(commands);
public string Description => $"宏命令({_commands.Count} 个子命令)";
public void Execute()
{
foreach (var cmd in _commands) cmd.Execute();
}
public void Undo()
{
// 按相反顺序撤销
foreach (var cmd in _commands.AsEnumerable().Reverse()) cmd.Undo();
}
}撤销/重做机制
// 命令管理器 - 支持撤销和重做
public class CommandManager
{
private readonly Stack<ICommand> _undoStack = new();
private readonly Stack<ICommand> _redoStack = new();
public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear(); // 执行新命令后清除重做栈
Console.WriteLine($" > 执行: {command.Description}");
}
public void Undo()
{
if (_undoStack.Count == 0)
{
Console.WriteLine("没有可撤销的操作");
return;
}
var command = _undoStack.Pop();
command.Undo();
_redoStack.Push(command);
Console.WriteLine($" > 撤销: {command.Description}");
}
public void Redo()
{
if (_redoStack.Count == 0)
{
Console.WriteLine("没有可重做的操作");
return;
}
var command = _redoStack.Pop();
command.Execute();
_undoStack.Push(command);
Console.WriteLine($" > 重做: {command.Description}");
}
public void PrintHistory()
{
Console.WriteLine("--- 操作历史 ---");
Console.WriteLine($"可撤销: {_undoStack.Count} 步");
Console.WriteLine($"可重做: {_redoStack.Count} 步");
}
}
// 使用示例
var livingRoomLight = new Light("客厅");
var bedroomLight = new Light("卧室");
var ac = new AirConditioner();
var manager = new CommandManager();
// 依次执行命令
manager.ExecuteCommand(new LightOnCommand(livingRoomLight));
manager.ExecuteCommand(new LightOnCommand(bedroomLight));
manager.ExecuteCommand(new SetTemperatureCommand(ac, 22));
// 撤销操作
manager.Undo(); // 撤销空调调温
manager.Undo(); // 撤销卧室灯
// 重做操作
manager.Redo(); // 重做卧室灯
manager.PrintHistory();命令队列
// 支持异步执行的命令队列
public class AsyncCommandQueue : IDisposable
{
private readonly Channel<ICommand> _channel;
private readonly CancellationTokenSource _cts;
private readonly Task _processingTask;
private int _processedCount;
public AsyncCommandQueue()
{
_channel = Channel.CreateBounded<ICommand>(new BoundedChannelOptions(1000)
{
FullMode = BoundedChannelFullMode.Wait
});
_cts = new CancellationTokenSource();
_processingTask = ProcessCommandsAsync(_cts.Token);
}
public int ProcessedCount => _processedCount;
public async Task EnqueueAsync(ICommand command)
{
await _channel.Writer.WriteAsync(command);
Console.WriteLine($"[队列] 命令已入队: {command.Description}");
}
public void Complete() => _channel.Writer.Complete();
private async Task ProcessCommandsAsync(CancellationToken cancellationToken)
{
await foreach (var command in _channel.Reader.ReadAllAsync(cancellationToken))
{
try
{
command.Execute();
Interlocked.Increment(ref _processedCount);
}
catch (Exception ex)
{
Console.WriteLine($"[队列] 命令执行失败: {command.Description} - {ex.Message}");
}
}
}
public async Task WaitUntilEmptyAsync()
{
while (_channel.Reader.Count > 0)
await Task.Delay(100);
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
// 使用命令队列
await using var queue = new AsyncCommandQueue();
await queue.EnqueueAsync(new LightOnCommand(new Light("厨房")));
await queue.EnqueueAsync(new SetTemperatureCommand(new AirConditioner(), 25));
await queue.EnqueueAsync(new LightOffCommand(new Light("客厅")));
queue.Complete();
await queue.WaitUntilEmptyAsync();
Console.WriteLine($"已处理 {queue.ProcessedCount} 个命令");MediatR Command 模式
MediatR 是 .NET 中实现 CQRS 和中介者模式的流行库,其 Command 模式是命令模式的现代化实现。
// 定义 Command
public record CreateOrderCommand(
string CustomerName,
List<OrderItemDto> Items,
string ShippingAddress) : IRequest<CreateOrderResult>;
public record OrderItemDto(string ProductId, int Quantity, decimal UnitPrice);
public record CreateOrderResult(int OrderId, decimal TotalAmount);
// Command Handler
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, CreateOrderResult>
{
private readonly IOrderRepository _orderRepository;
private readonly IInventoryService _inventoryService;
private readonly IMediator _mediator;
private readonly ILogger<CreateOrderCommandHandler> _logger;
public CreateOrderCommandHandler(
IOrderRepository orderRepository,
IInventoryService inventoryService,
IMediator mediator,
ILogger<CreateOrderCommandHandler> logger)
{
_orderRepository = orderRepository;
_inventoryService = inventoryService;
_mediator = mediator;
_logger = logger;
}
public async Task<CreateOrderResult> Handle(
CreateOrderCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("创建订单: {Customer}", request.CustomerName);
// 1. 计算总金额
var totalAmount = request.Items.Sum(i => i.Quantity * i.UnitPrice);
// 2. 检查库存
foreach (var item in request.Items)
{
var available = await _inventoryService.CheckStockAsync(item.ProductId);
if (available < item.Quantity)
throw new InvalidOperationException($"商品 {item.ProductId} 库存不足");
}
// 3. 创建订单
var order = new Order
{
CustomerName = request.CustomerName,
Items = request.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity,
UnitPrice = i.UnitPrice
}).ToList(),
TotalAmount = totalAmount,
ShippingAddress = request.ShippingAddress,
Status = OrderStatus.Pending
};
await _orderRepository.SaveAsync(order);
// 4. 发布领域事件(通知其他模块)
await _mediator.Publish(new OrderCreatedEvent(order.Id, order.TotalAmount),
cancellationToken);
return new CreateOrderResult(order.Id, totalAmount);
}
}在 API 中使用 MediatR Command
// Program.cs 注册 MediatR
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddScoped<IInventoryService, WarehouseInventoryService>();
// Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<ActionResult<CreateOrderResult>> Create(
[FromBody] CreateOrderRequest request)
{
var command = new CreateOrderCommand(
request.CustomerName,
request.Items,
request.ShippingAddress);
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = result.OrderId }, result);
}
[HttpGet("{id}")]
public async Task<ActionResult> GetById(int id)
{
// 使用 Query 获取订单详情
return Ok();
}
}Command Pipeline Behaviors(管道行为)
// 类似中间件的管道行为 - 验证、日志、性能监控
public class ValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
// 在命令执行前进行验证
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
// 验证通过,继续执行
return await next();
}
}
// 性能监控管道
public class PerformanceBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger;
public PerformanceBehavior(ILogger<PerformanceBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
var response = await next();
sw.Stop();
if (sw.ElapsedMilliseconds > 500)
{
_logger.LogWarning("慢请求: {Request} 耗时 {Ms}ms",
typeof(TRequest).Name, sw.ElapsedMilliseconds);
}
return response;
}
}优点
缺点
总结
命令模式通过将操作封装为对象,为系统带来了极大的灵活性。从简单的智能家居控制到复杂的 CQRS 架构,命令模式都能发挥重要作用。在 .NET 开发中,MediatR 库将命令模式的理念与现代应用架构完美结合,通过 Pipeline Behavior 机制实现了横切关注点的优雅处理。掌握命令模式及其在 MediatR 中的应用,是构建可维护、可扩展 .NET 应用的重要技能。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《命令模式详解》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《命令模式详解》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《命令模式详解》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《命令模式详解》最大的收益和代价分别是什么?
