簡說設計模式——命令模式

1、什麼是命令模式

  在說命令模式前咱們先來講一個小例子。不少人都有吃夜市的經歷,對於那些推小車的攤位,一般只有老闆一我的,既負責製做也負責收錢,我要兩串烤串多放辣,旁邊的人要了三串烤麪筋不要辣,過了一下子又來人要烤蔬菜……,當人多的時候記憶力很差的老闆確定就不知道誰要的啥、交沒交錢了;而去有店鋪的烤肉攤,點單的時候會有服務員來記錄咱們的菜單,而後再去通知燒烤師傅進行燒烤,這樣就不會出現混亂了,固然咱們也能夠隨時對菜單進行修改,此時只需服務員記錄後去通知烤肉師傅便可,因爲有了記錄,最終算帳仍是不會出錯的。git

  從這裏講,前者其實就是「行爲請求者」和「行爲實現者」的緊耦合,對於請求排隊或記錄請求日誌,以及支持可撤銷的操做來講,緊耦合是不太合適的,而命令模式偏偏解決了這點問題。ide

  命令模式(Command),將一個請求封裝爲一個對象,從而使你可用不一樣的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操做。UML結構圖以下:this

  其中,Invoker是調用者角色,要求該命令執行這個請求;Command是命令角色,須要執行的全部命令都在這裏聲明,能夠是接口或抽象類;Receiver是接收者角色,知道如何實施與執行一個請求相關的操做,任何類均可能做爲一個接收者;ConcreteCommand將一個接收者對象綁定與一個動做,調用接收者相應的操做,以實現Execute。spa

  1. Command類

  用來聲明執行操做的接口/抽象類。日誌

 1 public abstract class Command {
 2 
 3     protected Receiver receiver;
 4     
 5     public Command(Receiver receiver) {
 6         this.receiver = receiver;
 7     }
 8     
 9     //執行命令的方法
10     abstract public void execute();
11     
12 }

  2. ConcreteCommand類

  具體的Command類,用於構造傳遞接收者,根據環境需求,具體的命令類也可能有n個。code

 1 public class ConcreteCommand extends Command {
 2 
 3     //構造傳遞接收者
 4     public ConcreteCommand(Receiver receiver) {
 5         super(receiver);
 6     }
 7 
 8     //必須實現一個命令
 9     @Override
10     public void execute() {
11         receiver.action();
12     }
13 
14 }

  3. Invoker類

  接收命令,並執行命令。對象

 1 public class Invoker {
 2 
 3     private Command command;
 4     
 5     //接受命令
 6     public void setCommand(Command command) {
 7         this.command = command;
 8     }
 9     
10     //執行命令
11     public void executeCommand() {
12         command.execute();
13     }
14     
15 }

  4. Receiver類

  該角色就是幹活的角色, 命令傳遞到這裏是應該被執行的。blog

1 public class Receiver {
2     
3     public void action() {
4         System.out.println("執行請求!");
5     }
6 
7 }

  5. Client類

  首先定義一個接收者,而後定義一個命令用於發送給接收者,以後再聲明一個調用者,便可把命令交給調用者執行。接口

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //定義接收者
 5         Receiver receiver = new Receiver();
 6         //定義一個發送給接收者的命令
 7         Command command = new ConcreteCommand(receiver);
 8         //聲明調用者
 9         Invoker invoker = new Invoker();
10         
11         //把命令交給調用者執行
12         invoker.setCommand(command);
13         invoker.executeCommand();
14     }
15     
16 }

  運行結果以下:事務

  

2、命令模式的應用

  1. 什麼時候使用

  • 在某些場合,如要對行爲進行「記錄、撤銷/重作、事務」等處理時

   2. 方法

  • 經過調用者調用接收者執行命令,順序爲調用者→接收者→命令

   3. 優勢

  • 類間耦合,調用者角色與接收者角色之間沒有任何依賴關係
  • 可擴展性
  • 命令模式結合職責鏈模式能夠實現命令族解析任務;結合模板方法模式能夠減小Command子類的膨脹問題

   4. 缺點

  • 可能致使某些系統有過多的具體命令類

   5. 使用場景

  • 認爲是命令的地方均可以使用
  • 系統須要支持命令的撤銷/恢復操做時

   6. 應用實例

  • GUI中每個按鈕都是一條命令
  • 模擬CMD(DOS命令)
  • 訂單的撤銷/恢復
  • 觸發-反饋機制的處理

 3、命令模式的實現

  下面以上面提到的燒烤店模型爲例,使用命令模式編寫代碼實現。類圖以下:

  1. 調用者角色

  服務員類爲調用者角色,在其中定義一個訂單列表用於存儲客戶訂單信息,經過setOrder()方法設置訂單、cancelOrder()方法取消訂單、notifyExecute()方法下單。

 1 public class Waiter {
 2 
 3     private List<Command> orders = new LinkedList<>();
 4     
 5     //設置訂單
 6     public void setOrder(Command command) throws Exception {
 7         //經過反射得到雞翅的類
 8         String s1 = Class.forName("com.adamjwh.gofex.command.BakeChickenWingCommand").toString().substring(6);
 9         //獲取command訂單中的類
10         String s2 = command.toString().substring(0, command.toString().indexOf("@"));
11         
12         //這裏模擬雞翅賣完的狀況,當訂單中有雞翅時,撤銷訂單
13         if(s1.equals(s2)) {
14             System.out.println("【服務員:雞翅沒有了,請點別的燒烤】");
15             cancelOrder(command);//撤銷訂單
16         } else {
17             orders.add(command);
18             System.out.println("添加訂單:" + command.getBarbecuer() + "\t時間:" + new Date().toString());
19         }
20     }
21     
22     //取消訂單
23     public void cancelOrder(Command command) {
24         orders.remove(command);
25         System.out.println("取消訂單:" + command.getBarbecuer() + "\t時間:" + new Date().toString());
26     }
27     
28     //通知所有執行
29     public void notifyExecute() {
30         System.out.println("-----------------------訂單-----------------------");
31         for(Command command : orders) {
32             command.excuteCommand();
33         }
34     }
35 }

  2. 命令角色

 1 public abstract class Command {
 2 
 3     protected Barbecuer receiver;
 4     
 5     public Command(Barbecuer receiver) {
 6         this.receiver = receiver;
 7     }
 8     
 9     //執行命令
10     abstract public void excuteCommand();
11     
12     //獲取名稱
13     abstract public String getBarbecuer();
14     
15 }

  3. 接收者角色

  這裏的接收者角色就是燒烤師傅,提供「烤羊肉串」和「烤雞翅」的操做。

 1 public class Barbecuer {
 2     
 3     //烤羊肉
 4     public void bakeMutton() {
 5         System.out.println("烤羊肉串");
 6     }
 7     
 8     //烤雞翅
 9     public void bakeChickenWing() {
10         System.out.println("烤雞翅");
11     }
12     
13 }

  4. 具體命令

  這裏以烤羊肉串類爲例,提供了執行命令的方法。烤雞翅類同理,此處再也不贅述。

 1 public class BakeMuttonCommand extends Command {
 2 
 3     private String barbecuer;
 4     
 5     public BakeMuttonCommand(Barbecuer receiver) {
 6         super(receiver);
 7         barbecuer = "烤羊肉串";
 8     }
 9 
10     @Override
11     public void excuteCommand() {
12         receiver.bakeMutton();
13     }
14     
15     //獲取名稱
16     public String getBarbecuer() {
17         return barbecuer;
18     }
19     
20 }

  5. Client客戶端

  開店前準備即初始化烤肉師傅、服務員及命令類,顧客點菜後將菜單信息存入服務員的訂單上,假設雞翅賣完了(參考Waiter類),則將雞翅項從訂單上刪除(即「撤銷」),而後使用notifyExecute()方法通知烤肉師傅。

 1 public class Client {
 2 
 3     public static void main(String[] args) throws Exception {
 4         //開店前準備
 5         Barbecuer barbecuer = new Barbecuer();
 6         Command bakeMuttonCommand1 = new BakeMuttonCommand(barbecuer);
 7         Command bakeMuttonCommand2 = new BakeMuttonCommand(barbecuer);
 8         Command bakeChickenWingCommand1 = new BakeChickenWingCommand(barbecuer);
 9         Waiter waiter = new Waiter();
10         
11         //開門營業,顧客點菜
12         waiter.setOrder(bakeMuttonCommand1);
13         waiter.setOrder(bakeMuttonCommand2);
14         //這裏假設雞翅賣完了
15         waiter.setOrder(bakeChickenWingCommand1);
16         
17         //點菜完畢,通知廚房
18         waiter.notifyExecute();
19     }
20     
21 }

  運行結果以下:

  

 

  命令模式實際上是把一個操做的對象與知道怎麼執行一個操做的對象分隔開。至於命令模式使用時機,敏捷開發原則告訴咱們,不要爲代碼添加基於猜想的、實際不須要的功能。若是不清楚一個系統是否須要命令模式,通常就不要着急去實現它,事實上,在須要的時候經過重構實現這個模式並不困難,只有在真正須要如撤銷/恢復操做等功能時,把原來的代碼重構爲命令模式纔有意義。

 

  源碼地址:https://gitee.com/adamjiangwh/GoF

相關文章
相關標籤/搜索