112、23种设计模式之命令模式(20/23)

一、命令模式的定义

命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
核心思想:将命令的 ** 发出者(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)实现了灵活的撤销功能。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值