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