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

简介: 【愚公系列】2021年12月 二十三种设计模式(十四)-命令模式(Command Pattern)

文章目录

前言

一、命令模式(Command Pattern)

二、使用步骤

角色

示例

总结

优点

缺点

使用场景

前言

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


提示:以下是本篇文章正文内容,下面案例可供参考


一、命令模式(Command Pattern)

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


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


二、使用步骤

角色

1、抽象命令(Command)


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


2、具体命令(Concrete Command)


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


3、接收者(Receiver)


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


4、调用者(Invoker)


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


示例

image.png

命名空间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!
--------------------------------------------------

总结

优点

1、降低对象之间的耦合度,通过参数化的命令信息来驱动程序的运行;

2、新的命令可以很容易地加入到系统中;

3、可以比较容易地设计一个组合命令;

4、调用同一方法实现不同的功能。


缺点

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


使用场景

1、系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;

2、系统需要在不同的时间指定请求、将请求排队和执行请求;

3、系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。


相关文章
|
4月前
|
设计模式 存储 算法
Java设计模式-命令模式(16)
Java设计模式-命令模式(16)
|
4月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
4月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
5月前
|
设计模式 存储 Java
【十二】设计模式~~~行为型模式~~~命令模式(Java)
文章详细介绍了命令模式(Command Pattern),这是一种对象行为型模式,用于将请求封装成对象,实现请求发送者与接收者的解耦,从而降低系统耦合度、提高灵活性,并支持命令的排队、记录、撤销和恢复操作。通过案例分析、结构图、时序图和代码示例,文章展示了命令模式的组成部分、实现方式和应用场景,并讨论了其优点、缺点和适用情况。
|
6月前
|
设计模式 JavaScript API
js设计模式【详解】—— 命令模式
js设计模式【详解】—— 命令模式
53 6
|
7月前
|
设计模式
命令模式-大话设计模式
命令模式-大话设计模式
|
7月前
|
设计模式
设计模式-05建造者模式(Builder Pattern)
设计模式-05建造者模式(Builder Pattern)
|
7月前
|
设计模式 Java uml
必知的技术知识:JAVA【设计模式】命令模式
必知的技术知识:JAVA【设计模式】命令模式
33 0
|
7月前
|
设计模式 Java
Java设计模式之命令模式详解
Java设计模式之命令模式详解
|
3天前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。