天天看點

【愚公系列】2021年12月 二十三種設計模式(十四)-指令模式(Command Pattern)

文章目錄

前言

一、指令模式(Command Pattern)

二、使用步驟

角色

示例

總結

優點

缺點

使用場景

設計模式(Design pattern)是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人了解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統都是多赢的,設計模式使代碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

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

指令模式屬于行為型模式,它嘗試将一個請求封裝成一個對象,進而使您可以用不同的請求對客戶進行參數化。

在該設計模式中,請求以指令的形式包裹在對象中并傳給調用對象。調用對象尋找可以處理該指令的合适的對象,并把該指令傳給相應的對象請求執行。

1、抽象指令(Command)

定義指令的接口,聲明指令執行的方法;

2、具體指令(Concrete Command)

指令接口實作對象,需要維持對接收者的引用,并調用接收者的功能來完成指令要執行的操作;

3、接收者(Receiver)

真正執行指令的對象。任何類都可能成為一個接收者,隻要它能夠實作指令要求實作的相應功能;

4、調用者(Invoker)

要求指令對象執行請求,需要維持指令對象的引用,可以持有很多的指令對象。

【愚公系列】2021年12月 二十三種設計模式(十四)-指令模式(Command Pattern)
命名空間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)操作。