摘要:命令模式先來看下命令模式的定義命令模式將請求封裝成對象,以便使用不同的請求隊列或者日志來參數化其他對象。命令模式也支持可撤銷的操作。通過新增兩個方法,命令模式能夠支持這一點。
命令模式
題目: 現在要做一個智能家居控制遙控器,功能如下圖所示。
下圖是家電廠商提供的類,接口各有差異,并且以后這種類可能會越來越多。
觀察廠商提供的類,你會發現,好多類提供了 on()、off() 方法,除此之外,還有一些方法像 dim()、setTemperature()、setVolumn()、setDirection()。由此我們可以想象,之后還會有更多的廠商類,每個類還會有各式各樣的方法。
如果我們把這些類都用到遙控器代碼中,代碼就會多一大堆的 if 語句,例如
if slot1 == Light: light.on() elif slot1 == Hottub: hottob.jetsOn()
并且更嚴重的是,每次有新的廠商類加進來,遙控器的代碼都要做相應的改動。
這個時候我們就要把動作的請求者(遙控器)從動作的執行者(廠商類)對象中解耦。
如何實現解耦呢?
我們可以使用命令對象。利用命令對象,把請求(比如打開電燈)封裝成一個特定對象。所以,如果對每個按鈕都存儲一個命令對象,那么當按鈕按下的時候,就可以請求命令對象做相關的工作。此時,遙控器并不需要知道工作的內容是什么,只要有個命令對象能和正確的對象溝通,把事情做好就可以了。
下面我們拿餐廳點餐的操作來介紹下命令模式。
餐廳通常是這樣工作的:
顧客點餐,把訂單交給服務員
服務員拿了訂單,把訂單交給廚師。
廚師拿到訂單后根據訂單準備餐點。
這里我們把訂單想象成一個用來請求準備餐點的對象,
和一般對象一樣,訂單對象可以被傳遞:從服務員傳遞到訂單柜臺,訂單的接口只包含一個方法 orderUp()。這個方法封裝了準備餐點所需的動作。
服務員的工作就是接受訂單,然后調用訂單的 orderUp() 方法,她不需要知道訂單內容是什么。
廚師是一個對象,他知道如何準備準備餐點,是任務真正的執行者。
如果我們把餐廳想象成OO 設計模式的一種模型,這個模型允許將”發出請求的對象“和”接受與執行這些請求的對象“分隔開來。比如對于遙控器 API,我們要分隔開”發出請求的按鈕代碼“和”執行請求的廠商特定對象”。
回到命令模式我們把餐廳的工作流程圖轉換為命令模式的流程圖:這里 client 對應上一張圖的顧客,command 對應訂單,Invoker 對應服務員,Receiver 對應的是廚師。
命令模式先來看下命令模式的定義:
命令模式將”請求“封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式也支持可撤銷的操作。
通過上邊的定義我們知道,一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。要達到這一點,命令對象將動作和接收者包進對象中。這個對象只暴露一個 execute() 方法,當此方法被調用時,接收者就會進行這些動作。
命令模式類圖如下:
回到遙控器的設計:我們打算將遙控器的每個插槽,對應到一個命令,這樣就讓遙控器變成了調用者。當按下按鈕,相應命令對象的 execute() 方法就會被調用,其結果就是接收者(例如:電燈、風扇、音響)的動作被調用。
命令模式還支持撤銷,該命令提供和 execute() 方法相反的 undo() 方法。不管 execute() 做了什么,undo() 都會倒轉過來。
代碼實現 遙控器的實現class RemoteControl(object): def __init__(self): # 遙控器要處理7個開與關的命令 self.on_commands = [NoCommand() for i in range(7)] self.off_commands = [NoCommand() for i in range(7)] self.undo_command = None # 將前一個命令記錄在這里 def set_command(self, slot, on_command, off_command): # 預先給每個插槽設置一個空命令的命令 # set_command 命令必須要有三個參數(插槽的位置、開的命令、關的命令) self.on_commands[slot] = on_command self.off_commands[slot] = off_command def on_button_was_pressed(self, slot): command = self.on_commands[slot] command.execute() self.undo_command = command # 當按下開或關的按鈕,硬件就會負責調用對應的方法 def off_button_was_pressed(self, slot): command = self.off_commands[slot] command.execute() self.undo_command = command def undo_button_was_pressed(self): self.undo_command.undo() def __str__(self): # 這里負責打印每個插槽和它對應的命令 for i in range(7): print("[slot %d] %s %s" % (i, self.on_commands[i].__class__.__name__, self.off_commands[i].__class__.__name__)) return ""命令的實現
這里實現一個基類,這個基類有兩個方法,execute 和 undo,命令封裝了某個特定廠商類的一組動作,遙控器可以通過調用 execute() 方法,執行這些動作,也可以使用 undo() 方法撤銷這些動作:
class Command(object): def execute(self): # 每個需要子類實現的方法都會拋出NotImplementedError # 這樣的話,這個類就是真正的抽象基類 raise NotImplementedError() def undo(self): raise NotImplementedError() # 在遙控器中,我們不想每次都檢查是否某個插槽都加載了命令, # 所以我們給每個插槽預先設定一個NoCommand 對象 # 所以沒有被明確指定命令的插槽,其命令將是默認的 NoCommand 對象 class NoCommand(Command): def execute(self): print("Command Not Found") def undo(self): print("Command Not Found")
以下是電燈類,利用 Command 基類,每個動作都被實現成一個簡單的命令對象。命令對象持有對一個廠商類的實例的引用,并實現了一個 execute()。這個方法會調用廠商類實現的一個或多個方法,完成特定的行為,在這個例子中,有兩個類,分別打開電燈與關閉電燈。
class Light(object): def __init__(self, name): # 因為電燈包括 living room light 和 kitchen light self.name = name def on(self): print("%s Light is On" % self.name) def off(self): print("%s Light is Off" % self.name) # 電燈打開的開關類 class LightOnCommand(Command): def __init__(self, light): self.light = light def execute(self): self.light.on() def undo(self): # undo 是關閉電燈 self.light.off() class LightOffCommand(Command): def __init__(self, light): self.light = light def execute(self): self.light.off() def undo(self): self.light.on()
執行代碼,這里創建多個命令對象,然后將其加載到遙控器的插槽中。每個命令對象都封裝了某個家電自動化的一項請求:
def remote_control_test(): remote = RemoteControl() living_room_light = Light("Living Room") kitchen_light = Light("Kitchen") living_room_light_on = LightOnCommand(living_room_light) living_room_light_off = LightOffCommand(living_room_light) kitchen_light_on = LightOnCommand(kitchen_light) kitchen_light_off = LightOffCommand(kitchen_light) remote.set_command(0, living_room_light_on, living_room_light_off) remote.set_command(1, kitchen_light_on, kitchen_light_off) print(remote) remote.on_button_was_pressed(0) remote.off_button_was_pressed(0) remote.undo_button_was_pressed() remote.on_button_was_pressed(1) remote.off_button_was_pressed(1) remote.undo_button_was_pressed()
執行后輸出為:
[slot 0] LightOnCommand LightOffCommand [slot 1] LightOnCommand LightOffCommand [slot 2] NoCommand NoCommand [slot 3] NoCommand NoCommand [slot 4] NoCommand NoCommand [slot 5] NoCommand NoCommand [slot 6] NoCommand NoCommand Living Room Light is On Living Room Light is Off Living Room Light is On Kitchen Light is On Kitchen Light is Off Kitchen Light is On集合多個命令
通常,我們還希望能有一個開關一鍵打開所有的燈,然后也可以一鍵關閉所有的燈,這里我們使用 MacroCommand:
class MacroCommand(Command): def __init__(self, commands): # 首先創建一個 commands 的 list,這里可以存放多個命令 self.commands = commands def execute(self): # 執行時,依次執行多個開關 for command in self.commands: command.execute() def undo(self): # 撤銷時,給所有命令執行 undo 操作 for command in self.commands: command.undo()
測試開關集合:
def remote_control_test(): remote = RemoteControl() living_room_light = Light("Living Room") kitchen_light = Light("Kitchen") garage_door = GarageDoor() living_room_light_on = LightOnCommand(living_room_light) living_room_light_off = LightOffCommand(living_room_light) kitchen_light_on = LightOnCommand(kitchen_light) kitchen_light_off = LightOffCommand(kitchen_light) garage_door_open = GarageDoorOpenCommand(garage_door) garage_door_close = GarageDoorCloseCommand(garage_door) # 測試開關集合 party_on_macro = MacroCommand([living_room_light_on, kitchen_light_on]) party_off_macro = MacroCommand([living_room_light_off, kitchen_light_off]) remote.set_command(3, party_on_macro, party_off_macro) print("--pushing macro on--") remote.on_button_was_pressed(3) print("--pushing macro off--") remote.off_button_was_pressed(3) print("--push macro undo--") remote.undo_button_was_pressed()
當然,我們也可以使用一個列表來記錄命令的記錄,實現多層次的撤銷操作。
命令模式的用途 1. 隊列請求命令可以將運算塊打包(一個接收者和一組動作),然后將它傳來傳去,就像是一般的對象一樣。即使在命令對象被創建許久以后,運算依然可以被調用。我們可以利用這些特性衍生一些應用,例如:日程安排、線程池、工作隊列等。
想象一個工作隊列:你在某一端添加命令,然后在另一端則是線程。線程進行下面的動作:從隊列中取出一個命令,調用它的 execute() 方法,等待這個調用完成,然后將次命令對象丟棄,再取下一個命令
此時的工作隊列和計算的對象之間是完全解耦的,此刻線程可能進行的是音頻轉碼,下一個命令可能就變成了用戶評分計算。
2. 日志請求某些應用需要我們將所有的動作都記錄在日志中,并能在系統死機之后,重新調用這些動作恢復到之前的狀態。通過新增兩個方法(store()、load()),命令模式能夠支持這一點。這些數據最好是持久化到硬盤。
要怎么做呢? 當我們執行命令時,將歷史記錄存儲到磁盤,一旦系統死機,我們就將命令對象重新加載,并成批的依次調用這些對象的 execute() 方法。
比如對于excel,我們可能想要實現的錯誤恢復方式是將電子表格的操作記錄在日志中,而不是每次電子表格一有變化就記錄整個電子表格。數據庫的事務(transaction)也是使用這個技巧,也就是說,一整群操作必須全部進行完成,或者沒有任何操作。
參考鏈接命令模式完整代碼
最后,感謝女朋友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/41205.html
摘要:文本編輯器編輯器的三種模式命令模式末行模式和編輯模式命令模式控制光標移動,可對文本進行復制粘貼刪除和查找等工作。表示從環境變量中查找解釋器的位置,再調用該路徑下的解釋器來執行腳本。 Vim 文本編輯器 Vim 編輯器的三種模式——命令模式、末行模式和編輯模式 命令模式:控制光標移動,可對文本進行復制、粘貼、刪除和查找等工作。 輸入模式:正常的文本錄入。 末行模式:保存或退出文檔,以及...
摘要:文本編輯器編輯器的三種模式命令模式末行模式和編輯模式命令模式控制光標移動,可對文本進行復制粘貼刪除和查找等工作。表示從環境變量中查找解釋器的位置,再調用該路徑下的解釋器來執行腳本。 Vim 文本編輯器 Vim 編輯器的三種模式——命令模式、末行模式和編輯模式 命令模式:控制光標移動,可對文本進行復制、粘貼、刪除和查找等工作。 輸入模式:正常的文本錄入。 末行模式:保存或退出文檔,以及...
摘要:本篇主要講述中使用函數來實現策略模式和命令模式,最后總結出這種做法背后的思想。 《流暢的Python》筆記。本篇主要講述Python中使用函數來實現策略模式和命令模式,最后總結出這種做法背后的思想。 1. 重構策略模式 策略模式如果用面向對象的思想來簡單解釋的話,其實就是多態。父類指向子類,根據子類對同一方法的不同重寫,得到不同結果。 1.1 經典的策略模式 下圖是經典的策略模式的U...
摘要:類似這樣執行打印最終輸出的日志要想在命令行模式工作的時候,查看它的編譯進度,霖哥一般會遠程跑進執行編譯工作的機器,然后用命令,把它的日志實時輸出來嗯,這相當的不科學啊。我是霖哥,一個商學院畢業的程序員,一個游戲開發工程師。 showImg(https://segmentfault.com/img/remote/1460000008856262); 如果你使用過Unity命令行模式(ba...
摘要:該系列文章入門,編程基礎概念介紹變量,條件,函數,循環中的數據類型,,,,在中創建對象學一門編程語言正在變得越來越容易,只要念過高中甚至是初中小學,能熟練聊和懂得一點點軟件的人,入門一門編程語言都不在話下。 該系列文章: 《python入門,編程基礎概念介紹(變量,條件,函數,循環)》 《python中的數據類型(list,tuple,dict,set,None)》 《在python...
閱讀 2102·2021-11-19 09:58
閱讀 1701·2021-11-15 11:36
閱讀 2867·2019-08-30 15:54
閱讀 3386·2019-08-29 15:07
閱讀 2759·2019-08-26 11:47
閱讀 2805·2019-08-26 10:11
閱讀 2496·2019-08-23 18:22
閱讀 2744·2019-08-23 17:58