一、命令模式的定义
命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
核心思想:将命令的 ** 发出者(Invoker)和执行者(Receiver)解耦,中间通过命令对象(Command)** 进行沟通。命令对象封装了执行操作所需的所有信息。
四个关键角色:
(1)命令(Command):定义执行操作的接口(通常是一个 Execute 方法)。
(2)具体命令(ConcreteCommand):实现 Command 接口,绑定一个接收者和一个具体操作。调用接收者的相应方法来完成命令的执行。
(3)接收者(Receiver):知道如何执行一个请求相关的操作,真正执行命令的对象。
(4)调用者(Invoker):持有一个或多个命令对象,并在某个时间点调用命令的 Execute 方法。它不关心命令的具体执行逻辑。
二、应用场景
命令模式在以下场景中特别有用:
(1)需要解耦请求发送者和接收者:当你希望发送请求的对象(如按钮)不需要知道接收请求的对象(如窗口)是谁,以及它是如何处理请求的。
(2)需要支持撤销(Undo)和重做(Redo)操作:命令对象可以存储执行操作所需的状态,以便在需要时撤销或重做。
(3)需要支持事务(Transaction):将多个命令组合成一个复合命令,要么全部执行,要么全部不执行。
(4)需要将请求排队或记录请求日志:调用者可以将命令对象保存起来,稍后执行,或者记录下来用于审计或回放。
(5)需要动态地指定和执行请求:可以在运行时动态地创建不同的命令对象并交给调用者执行。
生活中的例子:
- 遥控器:你(调用者)按下按钮(具体命令),遥控器(命令接口)发送信号,电视(接收者)执行相应操作(开机、换台)。你不需要知道电视内部是如何实现的。
- 点餐:顾客(调用者)告诉服务员(命令接口)要点什么菜(具体命令),服务员将订单交给厨房(接收者),厨房按照订单做菜。
- 宏命令:在游戏中,你可以录制一系列操作(多个命令)为一个宏,之后一键执行(调用者调用这个复合命令)。
三、优缺点
1.优点
(1)解耦:彻底分离了请求的发送者和接收者,提高了系统的灵活性和可扩展性。
(2)可扩展性:很容易添加新的命令,只需创建新的 ConcreteCommand 类,而无需修改现有代码(符合开闭原则)。
(3)支持撤销 / 重做和事务:这是命令模式最强大的功能之一。
(4)命令可以被参数化、排队和记录:增加了系统的灵活性,可以实现更复杂的功能,如任务调度、日志审计等。
(5)便于测试:ConcreteCommand 对象可以独立于 Receiver 和 Invoker 进行单元测试。
2.缺点
(1)增加了类的数量:为每一个具体操作都创建一个 ConcreteCommand 类,可能会导致系统中类的数量急剧增加,增加了系统的复杂性。
(2)可能引入冗余:对于一些简单的命令,创建一个专门的类可能会显得有些繁琐。
四、C# 代码示例
我们以一个简单的计算器为例,实现加、减操作,并支持撤销功能。
1.角色定义
- ICommand (命令接口):定义 Execute 和 Undo 方法。
- AddCommand, SubtractCommand (具体命令):实现 ICommand 接口,分别执行加、减运算,并能撤销。
- Calculator (接收者):负责实际的计算逻辑(加、减)。
- User (调用者):持有命令对象,负责触发命令的执行和撤销。
2.代码实现
using System;
using System.Collections.Generic;
// 1. 命令接口 (Command)
public interface ICommand
{
void Execute();
void Undo();
}
// 2. 接收者 (Receiver)
// 计算器类,负责实际的计算逻辑
public class Calculator
{
private int _currentValue = 0;
public int CurrentValue => _currentValue;
public void Add(int value)
{
_currentValue += value;
Console.WriteLine($"计算器: 执行加法,当前值为: {_currentValue}");
}
public void Subtract(int value)
{
_currentValue -= value;
Console.WriteLine($"计算器: 执行减法,当前值为: {_currentValue}");
}
}
// 3. 具体命令 (ConcreteCommand) - 加法命令
public class AddCommand : ICommand
{
private readonly Calculator _calculator;
private readonly int _valueToAdd;
public AddCommand(Calculator calculator, int valueToAdd)
{
_calculator = calculator ?? throw new ArgumentNullException(nameof(calculator));
_valueToAdd = valueToAdd;
}
// 执行加法
public void Execute()
{
_calculator.Add(_valueToAdd);
}
// 撤销加法,即执行减法
public void Undo()
{
_calculator.Subtract(_valueToAdd);
}
}
// 3. 具体命令 (ConcreteCommand) - 减法命令
public class SubtractCommand : ICommand
{
private readonly Calculator _calculator;
private readonly int _valueToSubtract;
public SubtractCommand(Calculator calculator, int valueToSubtract)
{
_calculator = calculator ?? throw new ArgumentNullException(nameof(calculator));
_valueToSubtract = valueToSubtract;
}
// 执行减法
public void Execute()
{
_calculator.Subtract(_valueToSubtract);
}
// 撤销减法,即执行加法
public void Undo()
{
_calculator.Add(_valueToSubtract);
}
}
// 4. 调用者 (Invoker)
// 用户类,负责发起命令和管理命令历史(用于撤销)
public class User
{
private readonly Calculator _calculator;
private readonly Stack<ICommand> _commandHistory = new Stack<ICommand>();
public User(Calculator calculator)
{
_calculator = calculator ?? throw new ArgumentNullException(nameof(calculator));
}
// 执行一个新命令
public void ExecuteCommand(ICommand command)
{
command.Execute();
_commandHistory.Push(command);
}
// 撤销上一个命令
public void UndoLastCommand()
{
if (_commandHistory.Count > 0)
{
ICommand commandToUndo = _commandHistory.Pop();
Console.WriteLine("\n用户: 请求撤销上一步操作...");
commandToUndo.Undo();
}
else
{
Console.WriteLine("\n用户: 没有可撤销的操作。");
}
}
public void ShowCurrentValue()
{
Console.WriteLine($"\n当前计算结果: {_calculator.CurrentValue}");
}
}
// 客户端代码
class Program
{
static void Main(string[] args)
{
// 创建接收者
Calculator calculator = new Calculator();
// 创建调用者,并将接收者传入
User user = new User(calculator);
Console.WriteLine("--- 开始计算 ---");
user.ShowCurrentValue();
// 用户执行一系列命令
Console.WriteLine("\n用户: 输入 +5");
user.ExecuteCommand(new AddCommand(calculator, 5)); // 5
user.ShowCurrentValue();
Console.WriteLine("\n用户: 输入 -2");
user.ExecuteCommand(new SubtractCommand(calculator, 2)); // 3
user.ShowCurrentValue();
Console.WriteLine("\n用户: 输入 +10");
user.ExecuteCommand(new AddCommand(calculator, 10)); // 13
user.ShowCurrentValue();
// 用户决定撤销操作
user.UndoLastCommand(); // 回到 3
user.ShowCurrentValue();
user.UndoLastCommand(); // 回到 5
user.ShowCurrentValue();
user.UndoLastCommand(); // 回到 0
user.ShowCurrentValue();
// 再撤销就没了
user.UndoLastCommand();
Console.WriteLine("\n--- 计算结束 ---");
}
}
3.输出结果
--- 开始计算 ---
当前计算结果: 0
用户: 输入 +5
计算器: 执行加法,当前值为: 5
当前计算结果: 5
用户: 输入 -2
计算器: 执行减法,当前值为: 3
当前计算结果: 3
用户: 输入 +10
计算器: 执行加法,当前值为: 13
当前计算结果: 13
用户: 请求撤销上一步操作...
计算器: 执行减法,当前值为: 3
当前计算结果: 3
用户: 请求撤销上一步操作...
计算器: 执行加法,当前值为: 5
当前计算结果: 5
用户: 请求撤销上一步操作...
计算器: 执行减法,当前值为: 0
当前计算结果: 0
用户: 请求撤销上一步操作...
用户: 没有可撤销的操作。
--- 计算结束 ---
这个例子清晰地展示了命令模式如何将请求的发起者(User)和执行者(Calculator)解耦,并通过命令对象(AddCommand, SubtractCommand)实现了灵活的撤销功能。

6万+

被折叠的 条评论
为什么被折叠?



