写点什么

【愚公系列】2022 年 05 月 二十三种设计模式 (十四)- 命令模式 (Command Pattern)

作者:愚公搬代码
  • 2022 年 5 月 14 日
  • 本文字数:4437 字

    阅读完需:约 15 分钟

前言

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

一、命令模式(Command Pattern)

命令模式属于行为型模式,它尝试将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。


在该设计模式中,请求以命令的形式包裹在对象中并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象请求执行。

二、使用步骤

角色

1、抽象命令(Command)


定义命令的接口,声明命令执行的方法;


2、具体命令(Concrete Command)


命令接口实现对象,需要维持对接收者的引用,并调用接收者的功能来完成命令要执行的操作;


3、接收者(Receiver)


真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能;


4、调用者(Invoker)


要求命令对象执行请求,需要维持命令对象的引用,可以持有很多的命令对象。

示例


命名空间 CommandPattern 中包含 Command 基类、发票开具命令类 CreateCommand、发票作废命令类 CancelCommand、发票打印命令类 PrintCommand、命令参数基类 CommandArgs、发票开具命令参数类 CommandArgs、发票作废命令参数类 CancelArgs、发票打印命令参数类 PrintArgs、接收者类 Receiver 和调用者类 Invoker。本命尝试通过客户端调用不同的参数化发票命令来使调用者调用不同的功能。


public abstract class Command {
protected Receiver _receiver = null;
protected CommandArgs _commandArgs = null;
public Command(Receiver receiver, CommandArgs commandArgs) { this._receiver = receiver; this._commandArgs = commandArgs; }
public abstract void Action();
}
复制代码


抽象命令基类,包含 Action 动作执行命令,并且维持对接受者和命令参数的引用。


public class CreateCommand : Command {
public CreateCommand(Receiver receiver, CommandArgs commandArgs) : base(receiver, commandArgs) {
}
public override void Action() { _receiver.CommandArgs = _commandArgs; (_receiver as CreateReceiver)?.CreateInvoice(); }
}
复制代码


这是发票开具命令,由于基类维持了对调用者的引用,所以在 Action 方法中通过调用 CreateInvoice 方法来开具一张发票。


public class CancelCommand : Command {
public CancelCommand(Receiver receiver, CommandArgs commandArgs) : base(receiver, commandArgs) {
}
public override void Action() { _receiver.CommandArgs = _commandArgs; (_receiver as CancelReceiver)?.CancelInvoice(); }
}
复制代码


这是发票作废命令,由于基类维持了对调用者的引用,所以在 Action 方法中通过调用 CancelInvoice 方法来作废一张发票。


public class PrintCommand : Command {
public PrintCommand(Receiver receiver, CommandArgs commandArgs) : base(receiver, commandArgs) {
}
public override void Action() { _receiver.CommandArgs = _commandArgs; (_receiver as PrintReceiver)?.PrintInvoice(); }
}
复制代码


这是发票打印命令,由于基类维持了对调用者的引用,所以在 Action 方法中通过调用 PrintInvoice 方法来打印一张发票。


public class CommandArgs {
public string InvoiceType { get; set; }
}
复制代码


public class CreateArgs : CommandArgs {
public DateTime BillingDate { get; set; }
}
复制代码


public class CancelArgs : CommandArgs {
public string InvoiceCode { get; set; } public int InvoiceNumber { get; set; } public string CancelReason { get; set; } public string CancelMan { get; set; } public DateTime CancelDate { get; set; }
}
复制代码


public class PrintArgs : CommandArgs {
public string InvoiceCode { get; set; } public int InvoiceNumber { get; set; }
}
复制代码


参数化的命令参数基类 CommandArgs 和它的 3 个具体实现类。实际开发过程中可以将参数化命令信息封装在具体命令类中,本例为了更好的扩展性,将参数化命令信息抽象出来。


public class Invoker {
private Command _command = null;
public Invoker(Command command) { this._command = command; }
public void Execute() { _command.Action(); }
}
复制代码


调用者类 Invoker,实际开发中这个应为具体的调用类。例如我们需要从 MQ 获取实时数据,并根据从 MQ 获取到的 JSON 数据来处理不同的命令,那么这个调用者类应该为 MQ 所在的管理类(假如名为 ActiveMQManager)。这时我们需要在 ActiveMQManager 类中维持对命令基类的引用,并在收到不同的 JSON 数据时解析出相应命令和命令参数信息,然后执行命令中的 Action 方法。


public abstract class Receiver {
public CommandArgs CommandArgs { get; set; }
protected const string LINE_BREAK = "-------------------------" + "-------------------------"; //文章排版需要,故折成2行
}
复制代码


public class CreateReceiver : Receiver {     public void CreateInvoice() {        var args = CommandArgs as CreateArgs;        if (args == null) throw new InvalidOperationException();        Console.WriteLine("Create Invoice!");        Console.WriteLine(            $"InvoiceType is {args.InvoiceType},{Environment.NewLine}" +            $"BillingDate is {args.BillingDate.ToString("yyyy-MM-dd HH:mm:ss")}!");        Console.WriteLine(LINE_BREAK);    } }
复制代码


public class CancelReceiver : Receiver {     public void CancelInvoice() {        var args = CommandArgs as CancelArgs;        if (args == null) throw new InvalidOperationException();        Console.WriteLine("Cancel Invoice!");        Console.WriteLine(            $"InvoiceCode is {args.InvoiceCode},{Environment.NewLine}" +            $"InvoiceNumber is {args.InvoiceNumber},{Environment.NewLine}" +            $"InvoiceType is {args.InvoiceType},{Environment.NewLine}" +            $"CancelReason is {args.CancelReason},{Environment.NewLine}" +            $"CancelMan is {args.CancelMan},{Environment.NewLine}" +            $"CancelDate is {args.CancelDate.ToString("yyyy-MM-dd HH:mm:ss")}!");        Console.WriteLine(LINE_BREAK);    } }
复制代码


public class PrintReceiver : Receiver {     public void PrintInvoice() {        var args = CommandArgs as PrintArgs;        if (args == null) throw new InvalidOperationException();        Console.WriteLine("Print Invoice!");        Console.WriteLine(            $"InvoiceCode is {args.InvoiceCode},{Environment.NewLine}" +            $"InvoiceNumber is {args.InvoiceNumber},{Environment.NewLine}" +            $"InvoiceType is {args.InvoiceType}!");        Console.WriteLine(LINE_BREAK);    } }
复制代码


接收者基类 Receiver 和它的 3 个具体接收者类,需要维持对命令参数基类的引用,以便我们可以获取相应信息。接收者基类并不是命令模式必须的,但考虑到里氏替换原则和开闭原则,我们引入接收者基类并在不同的实现类里解耦不同的命令操作。


public class Program {
private static Receiver _receiver = null;
public static void Main(string[] args) { _receiver = new CreateReceiver(); Command command = new CreateCommand( _receiver, new CreateArgs { InvoiceType = "004", BillingDate = DateTime.UtcNow });
var invoker = new Invoker(command); invoker.Execute();
_receiver = new CancelReceiver(); command = new CancelCommand( _receiver, new CancelArgs { InvoiceCode = "310987289304", InvoiceNumber = 34156934, InvoiceType = "007", CancelReason = "Invoice missing!", CancelMan = "Iori", CancelDate = DateTime.UtcNow });
invoker = new Invoker(command); invoker.Execute();
_receiver = new PrintReceiver(); command = new PrintCommand( _receiver, new PrintArgs { InvoiceCode = "310987289304", InvoiceNumber = 34156934, InvoiceType = "026" });
invoker = new Invoker(command); invoker.Execute();
Console.ReadKey(); }
}
复制代码


以上是为了测试本案例所编写的代码,通过不同的命令并提供额外的参数化命令信息来执行不同的功能。以下是这个案例的输出结果:


Create Invoice!InvoiceType is 004,BillingDate is 2018-07-19 05:34:45!--------------------------------------------------Cancel Invoice!InvoiceCode is 310987289304,InvoiceNumber is 34156934,InvoiceType is 007,CancelReason is Invoice missing!,CancelMan is Iori,CancelDate is 2018-07-19 05:34:45!--------------------------------------------------Print Invoice!InvoiceCode is 310987289304,InvoiceNumber is 34156934,InvoiceType is 026!--------------------------------------------------
复制代码


<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

总结

优点

1、降低对象之间的耦合度,通过参数化的命令信息来驱动程序的运行;2、新的命令可以很容易地加入到系统中;3、可以比较容易地设计一个组合命令;4、调用同一方法实现不同的功能。

缺点

使用命令模式可能会导致某些系统有过多的具体命令类,导致子类膨胀。

使用场景

1、系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;2、系统需要在不同的时间指定请求、将请求排队和执行请求;3、系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2022.03.01 加入

该博客包括:.NET、Java、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、python、大数据等相关使用及进阶知识。查看博客过程中,如有任何问题,皆可随时沟通。

评论

发布
暂无评论
【愚公系列】2022年05月 二十三种设计模式(十四)-命令模式(Command Pattern)_5月月更_愚公搬代码_InfoQ写作社区