命令模式筆記-後篇(Command Pattern) | PIN - - 點部落

文章推薦指數: 80 %
投票人數:10人

那我不就有一個萬用遙控器了,這種自由度不是更棒嗎?」 ... 28: public void setCommand(int slot, Command onCommand, Command offCommand) 命令模式筆記-後篇 以下定義採自深入淺出設計模式一書:記得前篇介紹了簡單命令模式,他的圖長這樣: 記得前篇我們提出的創新需求是什麼:「將來這個廠牌假如出了一系列的產品,可能包含了5種,那在遙控器上設定五個插槽,每購買一台就附上一個插卡晶片而遙控器能讓我能自由插上所有我可以購買機器的話。

那我不就有一個萬用遙控器了,這種自由度不是更棒嗎?」 我們透過完成了命令模式的遙控器API後,我想就可以操控房間裡面的所有電器了吧…記得插糟嗎?上一篇我們為了操控CDPlayer,所以宣告了CDPlayerOnCommand那現在假如我有五項電器產品,包含了電腦、遊樂器、電扇、音響以及電燈的話,那該怎麼辦!計劃是這樣的:我們可以為遙控器的每一個插糟,都對應一個命令,讓遙控器變成Invoker(調用者),當按下遙控器的按鈕以後就呼叫對應命令物件的execute()方法,而接收者(那些器材)的動作就會被調用這個遙控器可能就是長成這個樣子,前一篇我們只有設計一個slot,讓CDPlayer插入他的晶片。

直覺上,我們可以透過陣列來存放這一些不同的器材與設備。

然後以類似的方式,將命令指定給遙控器,像下面一樣:1:onCommands[0]=onCommand; 2:offCommands[0]=offCommand; 陣列這麼好用的話,我們來實踐遙控器吧! 1:  2:  3:publicinterfaceCommand{ 4:publicvoidexecute(); 5:publicstringgetClass(); 6:} 7:  8: 9:publicclassMyRoomController 10:{ 11:Command[]onCommands; 12:Command[]offCommands; 13:publicMyRoomController() 14:{ 15://共有五個插糟 16:onCommands=newCommand[5]; 17:offCommands=newCommand[5]; 18:  19:CommandnoCommand=newNoComand(); 20:  21:for(inti=0;i<5;i++) 22:{ 23:onCommands[i]=noCommand; 24:offCommands[i]=noCommand; 25:} 26:} 27:  28:publicvoidsetCommand(intslot,CommandonCommand,CommandoffCommand) 29:{ 30:onCommands[slot]=onCommand; 31:offCommands[slot]=offCommand; 32:} 33:  34:publicvoidonButtonWasPushed(intslot) 35:{ 36:onCommands[slot].execute(); 37:} 38:  39:publicvoidoffButtonWasPushed(intslot) 40:{ 41:offCommands[slot].execute(); 42:} 43:  44:publicstringtoString() 45:{ 46://改寫ToString()印出插糟名稱與對應的命令! 47:StringBuildersb=newStringBuilder(); 48:sb.Append("\n---------遙控器--------\n"); 49:for(inti=0;i<5;i++) 50:{ 51:sb.Append("[Slot"+i+"]"+onCommands[i].getClass()+""+offCommands[i].getClass()+"\n"); 52:} 53:  54:returnsb.ToString(); 55:} 56:} 別忘了,遙控器也只是一個調用者,我們先來實踐一些接收者吧! 我們先實踐介面可能最單純的燈物件 1://例如這個燈物件 2:publicclassLight{ 3:stringLocation; 4:publicLight(stringLocation){ 5:this.Location=Location; 6:} 7:  8:publicvoidgetName(){ 9:returnthis.Location(); 10:} 11:  12:publicvoidoff(){ 13:Console.Write(this.Location+"的燈被關了\n"); 14:} 15:  16:publicvoidon(){ 17:Console.Write(this.Location+"的燈被開了\n"); 18:} 19:} 延續前篇,我們要實踐一個命令,讓人調用,所以也要宣告一個實踐Command介面的一個命令出來喲 1://燈就簡單多了,只有開跟關,當然你也可以寫一個更複雜,更多元的燈 2:publicclassLightOnCommand:Command 3:{ 4:Lightlight; 5:  6:publicLightOnCommand(Lightlight){ 7:this.light=light; 8:} 9:  10:publicvoidexecute(){ 11:this.light.on(); 12:} 13:  14:publicLightgetClass() 15:{ 16:returnthis.light; 17:} 18:} 那音響呢?這樣也要寫一個音響物件出來耶…沒錯,當你可以寫出來以後,相信你已經有能力可以完成你房間內所有電器的自動化的命令了! 1://音響介面 2:publicclassStereo{ 3:stringLocation; 4:  5:publicStereo(stringLocation) 6:{ 7:this.Location=Location; 8:} 9:  10:publicvoidgetName() 11:{ 12:returnthis.Location()+"的音響"; 13:} 14:  15:publicvoidoff(){ 16:Console.Write("音響關了\n"); 17:} 18:  19:  20:publicvoidon(){ 21:Console.Write("音響開了\n"); 22:} 23:  24: 25:publicvoidsetVolume(intn){ 26:Console.Write("音量調成了"+n.ToString()+"\n"); 27:} 28:  29: 30:publicvoidsetCD(stringcd){ 31:Console.Write("放入了"+cd+"的CD\n"); 32:} 33:} 好了,先這樣,我想,還不用測試,你就知道結果了吧!那我們測試先暫緩 剛剛onButtonWasPushed()的函式中,可能會需要這樣的程式碼: 1:publicvoidonButtonWasPushed(intslot) 2:{ 3:if(onCommands[slot]!=null) 4:onCommands[slot].execute(); 5:} 6:  7://空物件 8:publicclassNoCommand:Command 9:{ 10:publicvoidexecute(){} 11:publicvoidgetClass(){ 12:Console.write("沒有命令"); 13:} 14:} 15:  16://建構式中 17:CommandnoCommand=newNoComand(); 18:for(inti=0;i<5;i++) 19:{ 20:onCommands[i]=noCommand; 21:offCommands[i]=noCommand; 22:} 註:上面實踐了一種模式,NoCommand是一個空物件(nullobject),當需要一個沒有任何意義值時,空物件就很有用 Client也可將處理Null的責任轉移給空物件。

在這邊為例,遙控器不可能一出廠就知道你家有哪些電器需要命令來設定,所以提供了NoCommand物件作為代用品 他的責任就是,被執行到execute()的時候,什麼也不做,很多時候,空物件本身也是一種設計模式!   另外命令模式中還有一個特色要好好給他介紹一下: 命令是可以復原的!例如,電燈開了以後要復原他的命令,自然是把燈關閉了! 那關閉了音響以後,復原命令自然就是開啟音響囉! 既然命令都可以復原,那麼我們應該怎麼做呢??   在命令模式中實在是再簡單不多了!去針對介面寫程式吧! 1:publicinterfaceCommand{ 2:publicvoidexecute(); 3:publicvoidundo(); 4:  5:publicstringgetClass(); 6:} 相對的,命令物件就要這樣做: 1://燈就簡單多了,只有開跟關,當然你也可以寫一個更複雜,更多元的燈 2:publicclassLightOnCommand:Command 3:{ 4:Lightlight; 5:  6:publicLightOnCommand(Lightlight){ 7:this.light=light; 8:} 9:  10:publicvoidexecute(){ 11:this.light.on(); 12:} 13:  14:publicvoidundo() 15:{ 16:this.light.off(); 17:} 18:  19:publicstringgetClass() 20:{ 21:returnthis.light.getName(); 22:} 23:} 相信你也知道LightOffCommand要怎麼實作了。

為了因應這樣復原按鈕的支援,所以,必須對遙控器類別作一些修改(看綠色的註解處) 1:publicclassMyRoomController 2:{ 3:Command[]onCommands; 4:Command[]offCommands; 5://復原功能的小修改 6:CommandundoCommand; 7:  8:publicMyRoomController() 9:{ 10:onCommands=newCommand[5]; 11:offCommands=newCommand[5]; 12: 13:  14:CommandnoCommand=newNoComand(); 15:  16:for(inti=0;i<5;i++) 17:{ 18:onCommands[i]=noCommand; 19:offCommands[i]=noCommand; 20:} 21://復原功能的小修改 22:undoCommand=noCommand(); 23:} 24:  25:publicvoidsetCommand(intslot,CommandonCommand,CommandoffCommand) 26:{ 27:onCommands[slot]=onCommand; 28:offCommands[slot]=offCommand; 29:} 30:  31:publicvoidonButtonWasPushed(intslot) 32:{ 33:onCommands[slot].execute(); 34://復原功能的小修改 35:undoCommand=onCommands[slot]; 36:} 37:  38:publicvoidoffButtonWasPushed(intslot) 39:{ 40:offCommands[slot].execute(); 41://復原功能的小修改 42:undoCommand=offCommands[slot]; 43:} 當你所控制的物件狀態只有兩種的時候,自然不會有問題,就是用上上面的方式來處理命令的實踐(Light.onoroff) 但是假如你有先前執行的狀態的時候,通常想要實踐這樣的功能,需要去紀錄一些狀態,例如,Office的復原或是下一步的按鈕 想要回到上一步或下一步,就務必記錄下來狀態,因此,我們這時候,可以將物件的狀態變數,記錄在命令類別中。

例如: 1:publicclassElectronicFanSpeedHighCommand():Command 2:{ 3:ElectronicFanelectronicfan; 4://記錄狀態的變數。

5:stringprevSpeed; 6:  7:publicElectronicFanSpeedHighCommand(ElectronicFanElectronicFan){ 8:this.electronicfan=ElectronicFan; 9:} 10:  11:publicvoidexecute(){ 12:prevSpeed=electronicfan.getSpeed();//寫在風扇的物件方法中 13:electronicfan.speedHigh();//改變速度之前,要像上一行一樣記錄狀態。

14:} 15:  16:publicvoidundo(){ 17:if(prevSpeed==electronicfan.HIGH)//常數 18:electronicfan.speedHigh(); 19:if(prevSpeed==electronicfan.LOW)//常數 20:electronicfan.speedLow(); 21://以此類推 22:} 23:}   命令模式很容易的可以加入復原功能吧…而且將來需求又來的時候,只需要改到命令的實作類別而己。

  然而…遙控器會不會夢想太小呢? 既然都已經是萬用遙控器了,那家庭自動化如何呢?? 試想按下一個按鈕,就同時關燈,打開音響和電視,設定好DVD,並讓冷氣打開 或者是現在許多會議室都配置了自動化環境設定,可以快速進入會議模式、簡報模式或是上課模式的話 那這樣啟不是完美嗎?至少省去了很多試開關的動作呢…。

  再回到Office的應用…「巨集」就是這樣的一個概念。

將一連串的動作都”錄”下來,然後依序去執行,甚至…可以復原? 哇嗚,來實踐看看 1://巨集,一次不只執行一個命令,不錯吧 2:publicclassMacroCommand:Command 3:{ 4:Command[]commands; 5:publicMacroCommand(Command[]commands) 6:{ 7:this.commands=commands; 8:} 9:  10:publicvoidexecute() 11:{ 12://依序執行命令 13:for(inti=0;i



請為這篇文章評分?