举例说,在命令模式(Command Pattern)
在前面加上
???談到命令,大部分的人腦海中會想到以下這幅畫面
?
???這在現(xiàn)實生活中是一副諷刺漫畫,做決定的人不清楚運行決定的人有何特點,瞎指揮、外行領導內行說的就是這樣的。只是在軟件設計領域,我們顯然要為這樣的現(xiàn)象正名了,讓獅王能記住全部屬下的特點并直接打電話通知任務。顯然是為難他了,領導們非常忙。這是秘書處的工作。獅王僅僅要做出指示“近期鼠患猖獗。該抓抓了”,那秘書們就要起草紅頭文件(命令)并發(fā)給相關運行部門(貓),各司其職提高效率,公布請求的(秘書處)和運行請求的(貓)分離開來,將行為(抓鼠)封裝成對象(紅頭文件),這就是命令模式。
官方定義
? ? 將一個請求封裝成對象,從而可用不同的請求對客戶進行參數(shù)化,對請求排隊或記錄請求日志,以及運行可撤銷的操作——GOF23。
?
? ? 將請求封裝成對象,這個對象就是命令對象。在結構化程序中。請求一般是以函數(shù)的形式表現(xiàn)的。對于該請求中可能涉及到的運行對象,假設我們以函參的形式傳遞,這會造成下面幾個問題
? ? 1)緊耦合。客戶程序須要依賴運行對象,在上例中,上層在公布命令時須要依賴詳細的屬下,這會違反依賴倒置原則;把請求封裝成命令對象,這些命令對象遵循共同的命令接口,這就攻克了高層依賴問題。
? ? 2)函數(shù)沒用強調撤銷(undo)操作,函數(shù)中對對象狀態(tài)的保存須要額外的業(yè)務邏輯。
? ? 3)函數(shù)的復用性及擴展型較差,這也是為什么結構化逐漸被對象語言代替的原因。
??? 不同的請求能夠對客戶進行參數(shù)化,這個類似于策略模式的動態(tài)設置算法。在上例中。不同的請求被封裝成了不同的命令。用戶是能夠自由選擇當前要運行的命令是哪個。
“面向接口而不是實現(xiàn)”的編程原則,能夠在運行時依據(jù)上下文情況來選擇詳細命令。比方,獅王并不總和老鼠過不去,這段時間機關單位工作作風較差,遲到現(xiàn)象頻發(fā)。獅王就會指示“多打鳴,抓四風”,這時秘書處就會起草打鳴文件并保證其能夠下發(fā)運行。秘書處的職責,事實上就像是觸發(fā)器invoker(遙控器),他們起草何種文件并運行,就相當于觸發(fā)器綁定了何種命令對象。這樣的綁定關系是由Client(獅王)決定的。
?? 請求排隊、記錄日志、和可撤銷,這三點是命令模式的典型應用,在后文會提及,這個能夠從類似文本編輯軟件word中做比較,這些軟件的用戶界面設計廣泛借鑒了命令模式的特點。封裝請求成命令對象后,能夠把一系列的命令排隊、記錄、撤銷等。
角色
? ? 在該模式中包括下面幾個角色:
? ? Command —— 命令接口,上例中,相應于秘書處的紅頭文件的模板。當中定義了詳細命令所需實現(xiàn)的方法。如execute、undo等。
? ? ConcreateCommand —— 詳細命令,上例中,相應于抓鼠文件、打鳴文件。
這些命令中一般會包括接受者的引用,比方抓鼠紅頭文件會相應于一個貓的引用實例,execute方法會調用貓的捕鼠方法,打鳴同理。
? ? Client —— 創(chuàng)建詳細命令并設置接受者,這是命令的實際制定者。相應于獅王。每一個詳細命令對象在創(chuàng)建時。其接受者就已經被定義好了,在創(chuàng)建詳細命令時須要關心誰來運行。注意這里的Client并非通常所說的使用用戶,Client的作用類似于裝載器Loader,創(chuàng)建不同的命令對象并將其動態(tài)綁定在invoker中。
???Invoker?—— 要求命令運行的對象,相應于上例的秘書處,他們的任務是確保上通下達。對于每一個詳細的命令都要保證被運行。
???Receiver —— 接受者。命令的詳細運行者,相應于上例的阿雞阿貓,這些運行者一般會被組合在詳細命令中。他們有自己的方法,這些方法一般會在詳細命令的execute方法中被調用。
代碼實現(xiàn)
??實現(xiàn)的UML圖例如以下所看到的
?????
???以上圖為例,Command是命令接口。秘書類Secretary組合該接口對象,捕鼠和打鳴實現(xiàn)了該命令接口。因為邏輯比較簡單,直接貼代碼,首先是個簡單到不好意思的Command接口。
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public interface Command {public abstract void execute(); } </span></span></span>???詳細命令包含兩個,CatchMouseCommand和CrowCommand
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class CatchMouseCommand implements Command{Cat cat;public CatchMouseCommand(Cat cat) {// TODO Auto-generated constructor stubthis.cat = cat;}@Overridepublic void execute() {// TODO Auto-generated method stubcat.CatchMouse();}}</span></span></span>???And
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class CrowCommand implements Command{Cock cock;public CrowCommand(Cock cock) {// TODO Auto-generated constructor stubthis.cock = cock;}@Overridepublic void execute() {// TODO Auto-generated method stubcock.Crow();}}</span></span></span>?? 能夠看到這兩個詳細命令類都組合了接受者類。Cat或Cock,簡單起見,這兩個接受這類僅僅含有一個相應方法,例如以下
?? Cat
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//貓類-receiver public class Cat {//捕鼠方法public void CatchMouse() {System.out.println("Cat is catching mouse");} }</span></span></span>?? Cock
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//公雞類-receiver public class Cock {//打鳴方法public void Crow() {System.out.println("Cock is crowing");} }</span></span></span>???秘書類里組合了命令對象和兩個方法,setCommand是因為設置詳細命令,而publicCommand則類似于結構化語言中的回調,當須要運行該命令時這種方法就會調用詳細命令的execute函數(shù),秘書類作為請求發(fā)起者,并不關心詳細命令由誰運行,怎樣運行,這就實現(xiàn)了請求者和實現(xiàn)者的解耦。
invoker不關心receiver,反之亦然。
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class Secretary {Command command;public void setCommand(Command command) {this.command = command;}public void publicCommand() {this.command.execute();} }</span></span></span>???Lion在本例中是命令的其實的制定者。他創(chuàng)建了詳細命令并在合適時機交由秘書完畢命令公布和運行(receiver完畢),軟件開發(fā)實踐中這個類更像是一個裝載者,他建立一種映射關系,特定的invoker綁定特定的ConcreteCommand,后文會以word編輯器軟件開發(fā)進行分析。
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">/*** Lion不是實際的使用客戶,其作用相似于裝載器Loader,其作用是* 為觸發(fā)器Invoker(秘書對象secretary)綁定不同的命令對象(catchMouseCommand)。* 這就像在相似word軟件的菜單中,不同的菜單項MenuItem就是不同Invoker,每一個菜單項 * 都會綁定一個命令對象,甚至多個菜單項可能綁定同樣的命令對象。 * 用戶點擊時菜單項會觸發(fā)其綁定的命令對象方法。
*/ public class Lion { Secretary secretary; public Lion(Secretary secretary) { // TODO Auto-generated constructor stub this.secretary = secretary; } public void createCatchMouseOrder() { Cat cat = new Cat(); CatchMouseCommand catchMouseCommand = new CatchMouseCommand(cat); secretary.setCommand(catchMouseCommand); } public void createCrowCommand() { Cock cock = new Cock(); CrowCommand crowCommand = new CrowCommand(cock); secretary.setCommand(crowCommand); } }</span></span></span>
?? 完畢了上述的基本類后。我們須要加入用戶程序進行測試,例如以下
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class UserApplication {/** 這是通常意義上的用戶程序,也就是使用命令模式的上下文環(huán)境。* @param args*/public static void main(String[] args) {// TODO Auto-generated method stubSecretary secretary = new Secretary();//lion的作用相似Loader。為secretary(invoker)綁定命令對象。Lion lion = new Lion(secretary);lion.createCatchMouseOrder();secretary.publicCommand();lion.createCrowCommand();secretary.publicCommand();} }</span></span></span>???從測試例中能夠看到,lion為secretary動態(tài)綁定了不同的command,進而完畢請求參數(shù)化。在本例中當secretary接受到詳細的command就會publiccommand(公布運行),實際在非常多場合,這個公布是由用戶事件驅動的,比方某個MenuItem在初始化時綁定了相應功能。僅僅有在用戶點擊該MenuItem時,才會通過該菜單項的回調方法調用publiccommand,進而完畢實際的運行。
???該測試例運行結果例如以下:
??? Cat is catching mouse
??? Cock is crowing
擴展場景
???上述代碼演示樣例是命令模式的一個使用演示樣例,只解答了官方定義中的前半部分。即“請求參數(shù)化”,軟件實踐中命令模式應用較為廣泛的場景是文本編輯器或者是IDE用戶界面的開發(fā),以此為例解釋下官方定義中所說的請求排隊、記錄日志以及可撤銷操作。
?? “請求排隊”
?? 假設你在一個配置一般的機子上頻繁操作eclipse,通常你會看到以下這種界面
??
?
???后面一大推Waiting的任務,就是正在排隊的請求,這個現(xiàn)象的原因是以下這樣的圖:
??
?
???我們要求IDE運行的請求被封裝成命令對象放置在工作隊列中,每個空暇線程會得到并運行該命令對象,但資源有限。調度器須要限制可以使用的線程數(shù)量。當有新的線程空暇時。排隊等待的命令對象才會被順序運行,這就是命令模式在請求排隊中的應用方式。
??“記錄請求日志”
???這個主要是應用于大型數(shù)據(jù)庫的管理操作中。對于本文所舉的樣例實際意義不大。在大型數(shù)據(jù)庫的維護中,全部的操作修改都是以命令對象的方式進行的,有些修改必須是以事務的方式進行。由于這些修改彼此都是緊密聯(lián)系的,對于經年累月的頻繁修改,無法做到每次修改的數(shù)據(jù)庫內容都做一次備份,那樣須要太多資源,于是,把每次的修改命令對象以序列化方式保存(store)在磁盤上,每兩個checkpoint點之間的修改,都保存起來。
這樣在系統(tǒng)發(fā)生崩潰時,通過反序列化的方式從磁盤上裝載(load)這些命令對象,進而完畢這些命令的undo操作。這樣就完畢了數(shù)據(jù)庫系統(tǒng)恢復。
?? 完畢上述任務不僅須要命令對象支持序列化操作。并且對于Command接口也有了新的要求。例如以下
??
?
?
?? “可撤銷undo”Ctrl+Z
?? 還是舉前文的樣例。撤銷操作要求對象回到命令運行前的狀態(tài),這就須要在Command接口中加入undo方法。
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public interface Command {public abstract void execute();public abstract void undo(); //新增undo接口 }</span></span>?? 公雞類Cock內部須要一個表示打鳴頻率的實例。如果公雞打鳴頻率有高、中、低三種。通常情況下打鳴頻率為低,可能每周打鳴2次。可是命令下達后,打鳴頻率明顯升高。達到每周7次,這就是新的Cock類
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//公雞類-receiver public class Cock {public static final int LOW = 0;public static final int MEDIUM = 1;public static final int HIGH = 2;private int CrowFrequence; // 新添一個記錄打鳴頻率的狀態(tài)實例,默覺得LOW//打鳴方法public void Crow() {System.out.println("Cock is crowing");setCrowFrequence(HIGH); // 調用打鳴方法后,頻率為HIGH}//Getter和 Setter函數(shù),獲取和設置當前打鳴頻率public int getCrowFrequence() {return CrowFrequence;}public void setCrowFrequence(int crowFrequence) {CrowFrequence = crowFrequence;}@Overridepublic String toString() {// TODO Auto-generated method stubreturn "CrowFrequence is " + CrowFrequence;} }</span></span>???在詳細命令中。須要加入一個狀態(tài)來記錄命令運行前的打鳴頻率,在風頭過后運行undo操作時,這個被記錄的頻率值就被恢復了。
<span style="font-family:KaiTi_GB2312;font-size:18px;">public class CrowCommand implements Command{Cock cock;int PrevStage; //記錄在命令運行前的打鳴頻率狀態(tài)public CrowCommand(Cock cock) {// TODO Auto-generated constructor stubthis.cock = cock;}@Overridepublic void execute() {// TODO Auto-generated method stubPrevStage = cock.getCrowFrequence(); //運行命令前記錄。System.out.println("before execute : " + cock); cock.Crow(); System.out.println("after execute : " + cock); } @Override public void undo() { // TODO Auto-generated method stub cock.setCrowFrequence(PrevStage); //運行undo操作,設置保持的prevStage System.out.println("after undo : " + cock); } }</span>
? 改寫測試例加入運行undo操作,得到的結果例如以下
?? 這樣的undo情況比較簡單,不過保存了一個實例域。并且只能夠undo上一步,非常多時候須要建立操作的歷史記錄,這樣就須要保存一個運行command的鏈表,每個鏈表中都含有能夠撤銷的命令及其詳細實現(xiàn),這個在類似word和PS之類的軟件中應用的非常廣泛,不做贅述。
??“宏命令”
?? 這是一種特殊的命令類。在該類中定義一連串的命令list,運行和撤銷時。將該list中全部的命令都運行一遍。能夠自己定義加入或者刪除list中的詳細命令,熟練使用word的同學都用過宏命令,比方畢業(yè)論文模板對于字體、段落、頁眉頁腳等全部格式的要求能夠被簡單定義成一個宏模板,僅僅要應用這個宏模板就能夠高速自己主動的將當前文檔設置成論文要求格式。
結構
??????
???該結構和上文uml圖類似,可自行對照。
官方定義
效果及注意問題點
???1)命令模式將請求調用者和實際運行這解耦,符合依賴倒置原則。
???2)詳細的command對象能夠像其他對象一樣進行擴展。
這是一個把函數(shù)抽象成類的特殊對象,較普通函數(shù)優(yōu)勢前文已述。
???3)該結構符合開發(fā)封閉原則,加入新的類不須要修改原代碼結構。
???4)詳細命令對象的智能程度怎樣。這個命令對象是否一定依賴于receiver完畢操作,依賴程度是否會變化。
本文的詳細對象是全然依賴于接受者的方法。
???5)命令是否有必要進行undo操作。undo操作須要保持怎么的狀態(tài)值。假設狀態(tài)是較為復雜的對象,須要引入很多其他實例進行表示。在撤銷過程中是否會引起接受者內部的其他變化。
與其它模式的關系
?? 大型文本編輯軟件的菜單樹是分成復雜的,除了前文提到的宏命令以外,與組合模式相結合能夠實現(xiàn)具有多層分支結構的菜單樹命令。
?? 當用來記錄接受者的復雜狀態(tài)時,能夠使用備忘錄模式。利用該模式能夠完畢撤銷操作后的狀態(tài)恢復。
收尾
???命令模式是在軟件實踐中應用較為廣泛的一種模式,這個模式的應用場景較為特別,尤其是對于菜單樹和數(shù)據(jù)庫相關的功能模塊中,這樣的模式的長處明顯,它分離了任務的請求者和運行者,把行為封裝成對象,從而能夠完畢類似請求參數(shù)化、狀態(tài)保存/恢復、撤銷、重做、宏命令等功能。該模式最典型的應用多在軟件用戶菜單樹開發(fā)、事件驅動、數(shù)據(jù)庫日志維護及恢復等將行為作為命令對象的場合。甚至當你為程序加入一個ActionBar.TabListener監(jiān)聽器對象時,框架層也用到了命令模式。
歡迎分享交流,共同進步~
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 注:歡迎分享,轉載請聲明~~ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------版權聲明:本文博客原創(chuàng)文章,博客,未經同意,不得轉載。
總結
以上是生活随笔為你收集整理的举例说,在命令模式(Command Pattern)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Talend Open Studio
- 下一篇: 阅读《深入理解程序设计使用linux汇编