1. 程式人生 > >命令模式-將請求封裝成物件

命令模式-將請求封裝成物件

> **公號:碼農充電站pro** > **主頁:** 本篇來介紹**命令模式**(*Command Design Pattern*),它將“請求”封裝成物件,從而將“請求”的建立者與“請求”的執行者解耦。 ### 1,一次購物流程 相信大家都在網上買過東西,我們以**淘寶**為例來介紹命令模式。 我們假設這樣一個簡單的場景: - **淘寶網**有很多**商店**,商店售賣各種各樣的商品,顧客購買商品需要先在淘寶下**訂單**。 - 一位**顧客**想在淘寶購買一部**華為手機**,他下了一個訂單:“一部華為手機”。 - 淘寶網將該訂單傳送到華為商店。 - 華為商店將華為手機寄給了顧客。 在這個過程中,淘寶網並不關心每家商店的具體情況,它只知道每家商店都能完成它所派發的訂單。 那麼,我們怎樣為這個場景建模呢? ### 2,模擬購物流程 從上面購物流程中,我們能看出來,連線**顧客**,**淘寶網**與**商店**的中間橋樑是**訂單**: - 顧客在淘寶網生成一個訂單。 - 淘寶網將訂單發給具體商店。 - 商店將商品寄給顧客以完成訂單。 首先,我們需要定義一個 `Order` 介面: ```java interface Order { void execute(); } ``` `Order ` 介面中只有一個 `execute` 方法,需要派生類來實現。 然後定義一個華為商店: ```java abstract class Shops { protected String shopName; protected abstract String sell(); } class HuaWeiShop extends Shops { public HuaWeiShop() { this.shopName = "HUAWEI"; } public String sell() { return "HuaWei Phone"; } } ``` 上面程式碼中,`Shops` 是一個抽象類,表示商店,商店可以銷售商品。`HuaWeiShop` 類繼承了 `Shops` 介面,實現了 `sell` 方法。 然後定義一個 `GoodsOrder` 類,它繼承了 `Order` 介面,並實現了 `execute` 方法: ```java class GoodsOrder implements Order { private Shops shop; public GoodsOrder(Shops shop) { this.shop = shop; } public void execute() { String goods = shop.sell(); System.out.println(goods); } } ``` 然後定義 `Client` 類,用於生成訂單: ```java class Client { public Order createOrder() { Shops phone = new HuaWeiShop(); Order phoneOrder = new GoodsOrder(phone); return phoneOrder; } } ``` 下面定義淘寶網,它可以接收訂單和處理訂單: ```java class Taobao { private Order order; public void receiveOrder(Order order) { this.order = order; } // 處理訂單 public void handleOrder() { order.execute(); } } ``` 最後來測試程式碼: ```java Client c = new Client(); Order order = c.createOrder(); // 顧客生成訂單 Taobao t = new Taobao(); t.receiveOrder(order); // 淘寶接收訂單 t.handleOrder(); // 淘寶處理訂單 ``` 輸出如下: ```shell HuaWei Phone ``` 輸出表示顧客成功買到了手機。 我們畫出上面程式碼的類圖,如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201230100103832.png?) 我將完整的命令模式程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/Command.java),供大家參考。 ### 3,命令模式 實際上,上面程式碼的實現方式就是**命令模式**。 **命令模式將請求(命令)封裝為一個物件,這樣可以將不同請求注入到其他物件,並且能夠支援請求(命令)的排隊執行、記錄日誌、撤銷等功能**。 命令模式中包含以下幾個元件(並把元件類比到上面的購物場景中): - **Invoker**:請求的呼叫者,相當於 **Taobao**。 - **Command**:一個抽象介面,定義了所有具體請求必須實現的方法,相當於 **Order**。 - **ConcreteCommand**:一個具體的請求,相當於 **GoodsOrder**。 - **Receiver**:請求的接收者,用於真正的執行請求,相當於 **HuaWeiShop**。 - **Client**:請求的建立者。 命令模式的類圖如下(與上面購物程式碼的類圖一致): ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201230095939838.png?) 上圖的 **Command** 介面中有一個 `undo` 方法,它是 `execute` 方法的反操作,用於實現**撤銷功能**。 命令模式通過將請求封裝成物件,將請求的建立者,請求的呼叫者和請求的執行者,這三者之間徹底解耦: - **Client** 只負責請求的建立,而不關心請求何時何地被真正的執行。 - **Invoker** 只負責呼叫請求,而不關心請求是誰建立的,也不關心請求的真正執行者是誰。 - **Invoker** 也可以將一個請求拋棄掉,不去呼叫它 。 - **Receiver** 只負責執行請求,而不關心請求是誰建立的,也不關心請求是被誰呼叫的。 ### 4,請求服務 **請求服務**是一種由客戶端發出請求,然後由服務端去處理的一種程式架構,不同的客戶端之間互不干擾。 我們上面模擬的購物程式可以說使用的就是這種架構,如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201230120809479.png?) 比如 **Redis Server** 處理 **Client** 命令的方式使用的就是這種架構。 ### 5,請求佇列 請求被封裝成物件後,可將其放在**請求佇列**中,然後由**工作執行緒**將其取出,再執行。 這種架構也相當於一個**生產者-消費者**架構。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020123012163351.png?) ### 6,請求日誌 請求被封裝成物件後,也可以將其記錄在日誌中。如果服務意外崩潰,服務重啟後就可以使用請求日誌,將服務恢復到崩潰之前的狀態。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201230122324569.png?) 比如 **Redis** 的 **AOF 持久化**使用的就是這種方式。 ### 7,總結 命令模式將請求封裝成物件,有兩個優點: - 使得**請求的建立者**與請求的**呼叫/執行者**解耦。 - 使得請求可以輕鬆的被**傳遞和儲存**。 命令模式的這些優點,使得我們可以實現請求的排隊執行、記錄日誌等功能。 (本節完。) --- **推薦閱讀:** [單例模式-讓一個類只有一個物件](https://www.cnblogs.com/codeshell/p/14177102.html) [工廠模式-將物件的建立封裝起來](https://www.cnblogs.com/codeshell/p/14187677.html) [策略模式-定義一個演算法族](https://www.cnblogs.com/codeshell/p/14200531.html) [觀察者模式-將訊息通知給觀察者](https://www.cnblogs.com/codeshell/p/14205145.html) [裝飾者模式-動態的包裝原有物件的行為](https://www.cnblogs.com/codeshell/p/14210116.html) --- *歡迎關注作者公眾號,獲取更多技術乾貨。* ![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic