設計模式之命令模式(一)

  • 2019 年 12 月 25 日
  • 筆記

在本次學習過程中,我們把封裝帶到一個全新的境界:把方法調用(method invocation)封裝起來。沒錯,通過封裝方法調用,我們可以把運算塊包裝成形。

所以調用此運算的對象不需要關心事情是如何進行的,只要知道如何使用包裝成形的方法來完成它就可以。通過封裝方法調用,也可以做一些很聰明的事情,例如記錄日誌,或者重複使用這些封裝來實現撤銷。讓我們開始吧

現在有一個用戶A,他們家有很多家電,在裝修的時候,他讓裝修公司把這些家電當成了一個整體,想要通過一個遙控器就能控制家裡的電燈、風扇、熱水器、音響設備和其他的類似的可控制裝置,這樣的話就顯得很高大上。

所以呢,他讓每個廠商投提供了一組Java類,用來控制家電。手上又有一個遙控器,希望我們能夠創建一組控制遙控器的API,讓每個插槽都能控制一個或一組裝置,即通過一個遙控器就能控制所有家電,是不是很酷。

我們先來看下廠商給的類。

這個類確實不少呀,而且接口也各有差異。還有更麻煩的,隨着家電數量的增加,這樣的類還會越來越多。那麼,如何設計一個遙控器API就變得很有挑戰性了是吧。

所以,命令模式應運而生了。在我們的設計中,採用「命令模式」,利用命令對象,把請求(例如打開電燈)封裝成一個特定對象(例如客廳電燈對象)。所以,如果對每個按鈕都存儲一個命令對象,那麼當按鈕被按下的時候,就可以請命令對象做相關的工作。遙控器並不需要知道工作內容是什麼,只要有個命令對象能和正確的對象溝通,把事情做好就可以了。這樣,遙控器和電燈對象都解耦了。

可能把遙控器換成餐廳點餐,大家會更容易理解。我在這裡簡單描述下:首先顧客到了餐廳,根據菜單點了一部分菜,把菜單給服務員;服務員拿到了訂單,就提交到櫃檯上,並向後廚喊了一聲「xx號桌xx訂單」來了;後廚根據菜單進行配菜,燒菜。全稱服務員都不需要知道訂單具體內容是什麼,只要將桌上的客戶點的餐提供給後廚即可,這和我們的遙控器就是一個道理了。

第一個命令對象

實現命令接口

那我們就來創建我們的命令對象吧。首先,我們得讓所有的命令對象實現相同的包含一個方法的接口。在餐廳訂餐的例子上,就是創建訂單,我們在程序的世界裏,取名叫做execute()

public interface Command {      public void execute();  }  
實現一個打開電燈的命令

現在,假設想實現一個打開電燈的命令。根據之前看到的廠商提供的類,Light類有兩個方法,on和off。

// 這是一個命令,所以需要實現Command接口  public class LightOnCommand implements Command {      Light light;    // 構造器傳入某個電燈,以便讓這個命令控制,然後記錄在實例變量中      public LightOnCommand(Light light) {          this.light = light;      }    // 這個execute方法調用接收對象的on方法      public void execute() {          light.on();      }    }  

使用命令對象

現在,我們讓遙控器工作起來,先來點簡單的。假設遙控器只有一個按鈕和對應的插槽,可以控制一個裝置:

public class SimpleRemoteControl {  // 有一個插槽持有命令,而這個命令控制着一個裝置      Command slot;        public SimpleRemoteControl() {}    // 這個方法用來設置插槽控制的命令      public void setCommand(Command command) {          slot = command;      }    // 當按下按鈕時,這個方法就會被調用,使得當前命令銜接插槽,並調用它的execute方法      public void buttonWasPressed() {          slot.execute();      }  }  

就這樣我們就能實現一個簡單的遙控器了。請看我們的測試

public class RemoteControlTest {      public static void main(String[] args) {      // 遙控器就是調用者,會傳入一個命令對象,可以用來發出請求          SimpleRemoteControl remote = new SimpleRemoteControl();      // 現在創建一個電燈對象,此對象也就是請求的接收者          Light light = new Light();      // 這裡創建一個命令,然後將接收者傳給它          LightOnCommand lightOn = new LightOnCommand(light);        // 把命令傳給調用者          remote.setCommand(lightOn);      // 模擬按下按鈕          remote.buttonWasPressed();      }    }  

定義命令模式

經過訂餐流程的理解,以及剛才這個小練習,相信你也對命令模式內的類 和對象如何互動理解得很清楚了吧。那我們在這裡趁熱打鐵,定義一下命令模式。

命令模式將請求封裝成對象,以便使用不同的請求,隊列或者日誌來參數化其他對象。命令模式也支持可撤銷的操作。

仔細想想,我們知道有一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。要達到這一點,命令對象將動作和接收者包進對象中。這個對象只暴露出一個execute()方法,當此方法被調用的時候,接收者就會進行這些動作。從外面來看,其他對象不知道究竟哪個接收者進行了哪些動作,只知道如果調用execute()方法,請求的目的就能達到。

讓我們來看下命令模式的類圖:

好了,通過這個簡單的小練習,我們知道如何控制電燈的開關了。在前麵廠商給的類中,還有好多方法,比如電風扇、吊燈、電視機、音響等等。控制單個我們已經能搞定了,那控制多個呢?是不是同理呢?還是你有更好的方式呢?小編想請你先動動你的小手,我們下次見分曉。