二十三种设计模式[14]

前言

       命令模式,对象行为型模式的一种。它帮助我们将功能的调用者与实现者之间解耦(甚至完全解耦)。调用者与实现者之间并不是直接引用关系,调用者只需要知道如何发送当前功能的请求即可,而不用关心该请求由谁在何时完成。

       “ 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。”     

                                                                                                                                                             ——《设计模式 - 可复用的面向对象软件》

结构

Connand_1

  • Command(命令类接口):声明执行操作的接口;
  • ConcreteCommand(命令类):Command接口的实现,用来调用具体的实现;
  • Invoker(调用者):用来控制命令的执行,个人理解为Command的代理类;
  • Receiver(接收者):功能具体的实现,由ConcreteCommand调用;

示例

       考虑一个能够控制各种智能家电的App。在这个App中用户可以随意添加按钮来控制某个家电的某个功能。也就是说当我们开发这个App时并不能确定用户添加的按钮是控制什么家电执行什么功能。现在我们使用命令模式来模拟

Connand_2

/// <summary>
/// 命令的接收者
/// </summary>
public class Television
{
    public void Open()
    {
        Console.WriteLine("打开电视机");
    }

    public void Close()
    {
        Console.WriteLine("关闭电视机");
    }
}

/// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
    void Execute();
}

/// <summary>
/// 开机命令
/// </summary>
public class TvOpenCommand : ICommand
{
    private Television _tv = null;

    public TvOpenCommand(Television tv)
    {
        this._tv = tv;
    }

    public void Execute()
    {
        this._tv.Open();
    }
}

/// <summary>
/// 关机命令
/// </summary>
public class TvCloseCommand : ICommand
{
    private Television _tv = null;

    public TvCloseCommand(Television tv)
    {
        this._tv = tv;
    }

    public void Execute()
    {
        this._tv.Close();
    }
}

/// <summary>
/// 命令的调用者
/// </summary>
public class Button
{
    public ICommand Command { set; get; }

    public Button(ICommand command)
    {
        this.Command = command;
    }

    public void Click()
    {
        this.Command.Execute();
    }
}

static void Main(string[] args)
{            
    Television tv = new Television();           //创建电视机对象(命令的接收者)

    ICommand tvOpen = new TvOpenCommand(tv);    //创建开机命令
    ICommand tvClose = new TvCloseCommand(tv);  //创建关机命令

    Button button = new Button(tvOpen);         //创建开机按钮(命令的调用者)
    button.Click();                             //执行命令

    button.Command = tvClose;                   //将按钮功能变更为关机
    button.Click();                             //执行命令

    Console.ReadKey();
}

       在上述示例中,Button类充当调用者角色,Television类充当接收者角色。我们为Television类中的每一个函数都创建了命令类,不同的命令类决定了不同的操作,而该操作具体的实现由接收者完成。作为调用者的Button类并不知道它使用了哪个类执行了哪些操作,它只知道在它的Click函数中调用了ICommand接口的Execute函数。这就体现了命令模式的本质,将调用者与接收者解耦

  • 命令宏

           在日常生活中,我们往往希望通过一个按钮来执行一系列操作(比如一键打开电视和空调)。这个时候可以将命令模式与组合模式一同使用来实现一个命令宏(又称命令队列)。

Connand_3

/// <summary>
/// 命令的接收者
/// </summary>
public class Television
{
    public void Open()
    {
        Console.WriteLine("打开电视机");
    }

    public void Close()
    {
        Console.WriteLine("关闭电视机");
    }
}

/// <summary>
/// 命令接收者
/// </summary>
public class AirConditioner
{
    public void Open()
    {
        Console.WriteLine("打开空调");
    }

    public void Close()
    {
        Console.WriteLine("关闭空调");
    }
}

/// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
    void Execute();
}

/// <summary>
/// 开机命令
/// </summary>
public class TvOpenCommand : ICommand
{
    private Television _tv = null;

    public TvOpenCommand(Television tv)
    {
        this._tv = tv;
    }

    public void Execute()
    {
        this._tv.Open();
    }
}

/// <summary>
/// 关机命令
/// </summary>
public class TvCloseCommand : ICommand
{
    private Television _tv = null;

    public TvCloseCommand(Television t
    {
        this._tv = tv;
    }

    public void Execute()
    {
        this._tv.Close();
    }
}

/// <summary>
/// 开机命令
/// </summary>
public class AcOpenCommand : ICommand
{
    private AirConditioner _ac = null;

    public AcOpenCommand(AirConditioner ac)
    {
        this._ac = ac;
    }

    public void Execute()
    {
        this._ac.Open();
    }
}

/// <summary>
/// 关机命令
/// </summary>
public class AcCloseCommand : ICommand
{
    private AirConditioner _ac = null;

    public AcCloseCommand(AirConditioner ac)
    {
        this._ac = ac;
    }

    public void Execute()
    {
        this._ac.Close();
    }
}

/// <summary>
/// 命令宏
/// </summary>
public class MacorCommand : ICommand
{
    public List<ICommand> Commands { set; get; } = new List<ICommand>();

    public void Execute()
    {
        foreach (var command in this.Commands)
        {
            command.Execute();
        }
    }
}

/// <summary>
/// 命令的调用者
/// </summary>
public class Button
{
    public ICommand Command { set; get; }

    public Button(ICommand command)
    {
        this.Command = command;
    }

    public void Click()
    {
        this.Command.Execute();
    }
}

static void Main(string[] args)
{
    Television tv = new Television();                       //电视对象(命令接收者)
    AirConditioner ac = new AirConditioner();               //空调对象(命令接收者)

    TvOpenCommand tvOpenCommand = new TvOpenCommand(tv);    //电视开机命令
    AcOpenCommand acOpenCommand = new AcOpenCommand(ac);    //空调开机命令
    MacorCommand macorCommand = new MacorCommand();         //宏命令
    macorCommand.Commands.Add(tvOpenCommand);               //设置宏命令
    macorCommand.Commands.Add(acOpenCommand);               //设置宏命令

    Button button = new Button(macorCommand);               //创建宏按钮
    button.Click();                                         //执行命令

    Console.ReadKey();
}

      示例中创建一个聚合了ICommand接口的MacorCommand类充当组合模式中的Compsite角色,用来存储一系列的命令。它的存在能够使调用者一次执行多个使用不同接收者的命令。

      对于命令模式的扩展还有很多,比如请求日志和逆向操作(撤销)等等。这里就不一一举例了。

总结

       命令模式的核心思想是将一个请求封装,将一个请求命令的发出(调用)和接收处理分割开,达到将调用者与接收者解耦的目的。命令模式中的每个命令类都保持了一个很小的颗粒度,因为它只封装了一个接收类中的一个函数。好处是在开发的过程中调用者不必关心也不知道具体执行的函数,保证了调用者与接收者的松耦合状态,以便更好的控制和应对各种变化。同时也意味着令类需要封装的函数越多命令类也就越多,存在类爆炸的风险。

       以上,就是我对命令模式的理解,希望对你有所帮助。

       示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

       系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

       本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10031585.html)

原文地址:https://www.cnblogs.com/wxingchen/p/10031585.html