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