架構演化學習思考(3)

畅知發表於2024-08-01

架構演化學習思考(3)

接上一篇我們繼續對命令模式進行學習。

在這節內容中,我們聊一下經典的命令模式,還記得上一篇文章開頭我們實現的簡單的命令模式嗎?來看程式碼,非常簡單易解。

public interface ICommand
{
    void Execute();
}

public class PlayMusicCommand : ICommand
{
     public void Execute()
     {
        Debug.Log("你說家是唯一的城堡,隨著稻香一路奔跑~");
     }
}

var Start()
{
    var command = new PlayMusicCommand();
    command.Execute();
}

以上最簡的命令模式中,我們可以分離出一些角色。

ICommand:介面 對應經典命令模式中的 Command的角色

PlayMusicCommand:類 繼承介面,是介面的具體實現,對應經典命令模式中的ConcreteCommand角色。

Start方法:是命令的發出者,對應經典命令模式中的Invoker角色

由此我們提煉實現經典命令模式的三個角色。

經典命令模式的實現完善

還差一個Receiver,即命令的接收者或執行者,組成經典命令模式的四種(也有說五種)角色:

  • Command:抽象命令(命令介面)
  • ConcreteCommand:具體命令
  • Invoker:命令的呼叫者,發起者,觸發者。
  • Receiver:命令的接收者,被Command訪問和操作。
  • 客戶端:建立具體命令物件並設定其接收者,將命令物件交給呼叫者執行(如果涉及到5部分的話)

我們還以商品購買為例,用以上涉及到的角色方式來實現購買貨品這樣一個操作。

namespace TestCommand
{
    //售貨員 對應Receiver
    public class Salesperson
    {
        public void SellGoods(int id,int count)
        {
            for (int i = 1; i <= count; i++)
            {
                Debug.Log($"編號為{id}的商品售出1件!");
            }
            Debug.Log($"編號為{id}的商品總售出{count}件!");
        }
    }

    //命令介面  對應Command
    public interface Command
    {
        void Execute();
    }
    
    //具體實現 對應ConcreteCommand 具體命令
    public class BuyCommand : Command
    {
        public Salesperson salesPerson;
        public int goodsId;
        public int count;
        public void Execute()
        { 
            salesPerson.SellGoods(goodsId,count);
        }
    }
    
    //觸發者 命令的傳送者  
    //顧客
    public class Customer
    {
        private List<Command> mCommands = new List<Command>();

        public void AddCommand(Command command)
        {
            mCommands.Add(command);
        }
        
        //觸發命令
        public void triggerCommands()
        {
            mCommands.ForEach(command=>command.Execute());
            
            mCommands.Clear();
        }
    }

    //客戶端角色 
    void Start()
    {
        var customer = new Customer();
        var salesperson = new Salesperson();
        
        //2 號商品購買五件 的命令
        customer.AddCommand(new BuyCommand()
        {
            salesPerson = salesperson,
            goodsId = 2,
            count = 5
        });
        
        //讓顧客發出購買命令
        customer.triggerCommands();
    }

}

其大體思路如下:對應著經典命令模式的五部分

image

終於將經典模式的五部分實現完整了,命令模式梳理到這裡差不多結束了,那麼這對架構設計有啥啟發和應用思考呢?

當然有。

在專案中,我們對位於底層部分的資料模組訪問時就可以應用命令模式,Recever為底層的System或者資料Model,ConCreteCommand為對應的具體操作(對資料進行查和改、解鎖成就係統的成就),而觸發器Involver則是架構,也就是說整個專案的依賴關係的總掌控者,而客戶端則對應表現層的控制邏輯。

不知道有沒有對”架構“這個觸發者的認知有沒有更加具體一些呢?

筆者是這樣理解”架構“的身份的,好比是一個交換機接線員,當我們需要和朋友電話時候,則要拿起自己這邊的話筒傳呼接線員,告訴TA自己朋友的電話機號,然後接通之後,完成我們對朋友交流的需求。當然這個例子不一定十分準確,但很大程度上讓大家對”架構“的認識沒有那麼抽象。

好,在回到經典模式,來看一看此模式有什麼好處。

命令模式的好處

好處之一是將Invoker和Receiver完全解耦。

那這個功能好像觀察者模式也可以實現吧,那麼和觀察者模式對比有些不同呢?答案還在命令本身,命令除了將invoker和receiver解耦,還可以進行自由擴充套件。我可以購買、也可以退貨,也可以更換商品等等一些操作。當然從Invoker這邊可以對命令進行儲存(使用堆或者棧或者List等容器),進而可以實現回退復原、行為樹等功能。

關於命令模式的另一點思考:

我們將視角聚焦在命令模式的Command中,也就是結構圖上的”訂單模板“。有了模板就好進行擴充,這裡簡單聊聊,命令模式中的開閉原則。

命令模式中的開閉原則

開閉原則大家比較熟悉:

開閉原則:一個類應當對擴充套件開放、對修改關閉

當一個結構模組或系統開發成型之後,除非遇到一些bug或者功能缺陷,否則不應該對結構模組或者系統進行修改,這就是對修改關閉。而需要新增加功能或者擴充時候,可以透過擴充套件的方式來新增一些功能,也就是對擴充套件開放。

而根據命令模式中的各個角色,在擴充功能時候有著不同的準則和作用:

  • Invoker:關閉修改
  • Recever:關閉內部修改
  • Command: 擴充的標準
  • ConcreteCommand :開放實現的擴充

筆者的專案功底尚淺,只是簡單的聊一下涉及到開閉原則,願大家多寫程式碼多實踐,

慢慢將這些思考在實際專案中有所應用和體驗,逐步提高自己的程式設計和架構設計能力。

好,關於命令模式我們就聊到這裡了,接下來還會繼續更新此係列內容,謝謝各位與我一起思考和體悟!

相關文章