目錄
1 命令模式
命令模式
將”請求”封裝成對象(指命令對象
,能把方法調用封裝起來),以便使用不同的請求、隊列或者日誌來參數化其他對象
(指調用者對象
)。命令模式也支持可撤銷的操作。
- 一個
命令對象Command需要綁定一個命令接收者Receiver
,將接收者
和接收者的具體動作封裝進來
,只暴露出一個execute()
方法,當此方法被調用時,就會執行接收者的動作。這樣,當調用者對象
調用命令對象的execute()方法時,不需要知道是哪個接收者接收到了請求命令
,也不需要知道接收者執行了哪些動作
。 - 通過一個
抽象基類的命令接口類,可以擴展出許多具體的命令類
,這樣就只需要使用接口類對象來參數化調用者對象
即可。調用者對象不需要知道具體的命令,只知道他是一個命令即可
。 - 可以
通過命令模式來實現“隊列、日誌和支持撤銷操作”
。
在安裝嚮導、訂單系統、遙控控制系統、日程安排、工作隊列等諸多現實生活場景中有應用。
2 命令模式的UML類圖
Client
: 客戶端類,創建一個具體的命令對象ConcreteCommand和接收者對象Receiver,並將接收者對象綁定到具體的命令對象中。Receiver
: 接收者類,接收者具有執行請求命令的具體方法action()
。Command
: 命令對象的抽象基類,所有的具體命令對象都需要實現此接口,擁有一個execute()方法,調用此方法就可以讓接收者進行相關的動作,調用undo()方法可以讓接收者進行撤銷動作。ConcreteCommand
: 具體的命令對象類,實現了Command接口。與接收者綁定起來,調用者調用命令對象的execute()方法
就能發出請求命令
,然後由命令接收者Receiver接收命令
並執行相關的動作action()
。Invoker
: 調用者類,傳入一個具體的命令對象參數到方法setCommand()中
,並在某個時間點調用命令對象的execute()方法
。
使用步驟:
- 客戶端
實例化一個接收者對象receiver
和一個具體的命令對象command
,將receiver綁定到command中
。 實例化一個調用者invoker
,並在某個時間點使用一個命令對象傳入到setCommand(command)方法
中,然後調用命令對象command的execute()方法
,發出請求命令
。在命令對象的execute()方法中,調用與其綁定的接收者的action()方法,執行命令
。
優勢:
- 命令模式
將發出請求的調用者對象
和執行請求的接收者對象
解耦`。 - 被
解耦的兩個對象通過命令對象溝通
。命令對象封裝了接收者的一個或多個動作
。 - 調用者調用命令對象的execute()方法發出請求,這使得接收者的動作被調用。
- 命令對象可以
支持撤銷
,做法是實現一個 undo()方法來回到execute()被執行前的狀態
。 宏命令使用一系列命令對象初始化
,然後調用多個命令
。
3 觀察者的一個例子:遙控家電
遙控器有7個插槽,每個插槽對應兩個按鈕,每個按鈕對應到一個命令,這樣遙控器就充當了調用者的身份。當按下按鈕時,相應命令對象的execute()方法被調用,與其綁定的接收者(例如“電燈”、“天花板電扇”、“音響”)的動作就會被調用。還有一個撤銷按鈕,當按下時調用命令對象的undo()方法,接收者執行相關的動作。
3.1 基本功能實現
3.2 撤銷按鈕功能實現
3.3 宏命令功能實現
創建一個命令對象,當調用此命令對象的execute()方法後,會執行一系列命令。
4 隊列請求和日誌請求
4.1 隊列請求
如果有一個工作隊列:在一端添加命令,在另一端則是線程。線程進行下面的動作:從工作隊列中選擇一個命令,然後調用execute()方法,等待調用完成後,將此命令對其,再取出下一個命令。兩個相鄰的命令之間無需有任何關係。前一個命令可以是讀取網絡數據,下一個命令可以是處理財務運算。
4.2 日誌請求
某些應用需要將所有動作記錄在日誌中。當系統死機時,重新調用這些動作恢復到之前的狀態。只需要在命令中新增兩個方法store()和load()即可完成。
5 命令模式的一個Python實現例子
5.1 例子解釋
假如你是證券交易所的客戶,會創建買入股票和賣出股票的訂單(即Command)。通常情況下,你通過代理或經紀人(即Invoker)向證券交易所(即Receiver)溝通,而不是直接到證券交易所執行交易。代理負責將你的請求提交給證券交易所,完成股票買入或賣出請求。
5.2 UML類圖
5.3 代碼實現
- 接收者類StockTrade
- 定義了具體的動作
buy()和sell()
,由具體命名對象的execute()調用此方法
,然後執行具體動作
- 定義了具體的動作
class StockTrade:
'''
Receiver接受者,是一個知道如何做必要的工作的類
'''
def buy(self):
print("You will buy stocks.")
def sell(self):
print("You will sell stocks.")
- 命令接口類Order
抽象基類,具有一個execute()方法
from abc import ABCMeta, abstractmethod
class Order(metaclass=ABCMeta):
'''
命令Command的抽象基類,即爲具體的命令類提供了一個接口
'''
@abstractmethod
def execute(self):
'''通過調用命令對象的execute()方法,可以讓接收者進行相應的工作'''
pass```
- 具體的命令對象類BuyStockOrder、SellStockOrder
- 首先
構造函數__init__(stock)
使用一個接收者對象stock初始化屬性stock
實現的execute()方法
中調用stock的buy()或sell()方法
- 首先
class BuyStockOrder(Order):
'''具體命令對象類'''
def __init__(self, stock):
'''通過此方法將接收者綁定'''
self.stock = stock
def execute(self):
'''命令執行方法,通過調用此方法`發出命令執行請求`,然後`調用接收者的一個或多個動作`,由`接收者執行請求`'''
self.stock.buy()
class SellStockOrder(Order):
'''具體命令對象類'''
def __init__(self, stock):
'''通過此方法將接收者綁定'''
self.stock = stock
def execute(self):
'''命令執行方法,通過調用此方法`發出命令執行請求`,然後`調用接收者的一個或多個動作`,由`接收者執行請求`'''
self.stock.sell()
- 調用者類Agent
- 首先
構造函數__init__()
初始化__orderQueue屬性爲一個空列表
- 傳入一個具體命令對象給
placeOrder(order)方法
,並在某一時刻調用此方法發出請求
,然後執行命令對象的execute()方法
,在execute()方法
中,執行接收者的動作完成請求
。
- 首先
class Agent:
'''
Invoker調用者
'''
def __init__(self):
self.__orderQueue = []
def placeOrder(self, order):
'''調用者擁有一個命令對象列表,在某個時刻調通過此方法調用命令對象的execute()方法,將請求付諸實行'''
self.__orderQueue.append(order)
order.execute()
- 客戶端
- 客戶端
實例化一個接收者對象stock
和具體的命令對象buyStock、sellStock
,將stock綁定到具體命令中
。 實例化一個調用者agent
,並在某個時間點將一個命令對象傳入到placeOrder(order)方法
中,然後調用命令對象的execute()方法
,發出請求命令
。在命令對象的execute()方法中,調用與其綁定的接收者的buy()方法或sell()方法,執行命令
。
- 客戶端
if __name__ == '__main__':
# Client
stock = StockTrade() # 客戶先實例化一個接收者對象
buyStock = BuyStockOrder(stock) # 然後將接收者對象與具體的命令對象綁定起來
sellStock = SellStockOrder(stock) # 將接收者對象與具體的命令對象綁定起來
# Invoker
agent = Agent() # 實例化一個調用者對象
agent.placeOrder(buyStock) # 將命令對象與調用者綁定起來,調用者`發出命令執行請求`
agent.placeOrder(sellStock) # 將命令對象與調用者綁定起來,`發出命令執行請求`