1. 程式人生 > >淺談設計模式(職責鏈與命令模式)

淺談設計模式(職責鏈與命令模式)

 菜菜又開始寫設計模式的文章啦,今天和大家談談本人對職責鏈模式與命令模式的理解,並給出簡易的demo(demo涉及到到菜菜,貓貓,濤濤,望見諒)。

為什麼需要把這兩個模式放在一起寫呢,其實只是菜菜在學習這兩個模式的時候,對於思想上的理解有些混淆而已,不過他們兩者還是有一定關聯的:

1.RC(Responsibility Chain)與Command模式都屬於設計模式中的行為模式,所謂行為模式,即要描述物件或類的模式,而且要描述物件間的通訊模式

2.職責鏈模式使一個特定的請求接收物件對請求或命令的執行變得不確定,鬆耦合,而命令模式使得一個特定的物件與一個命令的執行變得明顯和確定。 在Java中,異常捕獲(try catch字句),就是用了職責鏈模式;而SWT的事件驅動機制則很好的運用了命令模式(菜菜的demo中也會設計到回撥的實現)

好了,邊上程式碼邊和大家一起學習

職責鏈模式: 

濤濤經常在群裡面問問題,假設,群裡面就我和貓負責回答問題(當然,我們的群大家都很活躍),而且我一般只知道java方面的知識,貓比較精通.net。 於是乎,濤濤每次在群裡面問問題的時候,都會標註是問題型別,問題內容(如:Java,java是什麼?)。問題提出後,濤並不知道誰會回答,但是隻要是符合我和貓胃口的,他總能得到滿意答案。 於是乎,菜菜,貓,濤三人之間的職責鏈模式開始了

Code:
  1. publicclass ProgramRequest {  
  2. publicstaticfinalint DOTNET_QUESTION = 1;  
  3. publicstaticfinalint JAVA_QUESTION = 2;  
  4. privateint m_questionType;  
  5. private String m_content;  
  6. public ProgramRequest(int type,String message){  
  7.     this.m_questionType = type ;  
  8.     this.m_content = message;  
  9. }  
  10. publicint getType(){  
  11.     return
     m_questionType;  
  12. }  
  13. public String getContent(){  
  14.     return m_content;  
  15. }  
  16. }  

以上程式碼只是個負責標誌濤濤提問請求的一個功能類,類似與javabean的功能,這裡只做演示,所以屬性較少。不多做解釋。

Code:
  1. publicabstractclass IProgramHandler {  
  2.     protected String m_name;  
  3.     protected IProgramHandler m_nextHandler;  
  4.     public IProgramHandler(String name){  
  5.         this.m_name = name ;  
  6.     }  
  7.     public String getName(){  
  8.         return m_name;  
  9.     }  
  10.     publicvoid setNextHandler(IProgramHandler handler){  
  11.         this.m_nextHandler = handler;  
  12.     }  
  13.     public IProgramHandler getNextHandler(){  
  14.         return m_nextHandler;  
  15.     }  
  16.     publicabstractvoid  Handler(ProgramRequest request);  
  17. }  

以上程式碼是一個負責處理請求的介面,這裡,菜菜用了抽象類來實現,各位也可以用Interface。 需要解釋的是,該類裡面有實際方法也有抽象方法,實際方法中getNextHandler是負責傳遞下一個任務處理者。 而handler這個抽象方法,就是菜菜和貓這兩個類去負責實現。

菜菜處理類:

Code:
  1. publicclass CaiCaiHandler extends IProgramHandler {  
  2.     public CaiCaiHandler(String name) {  
  3.         super(name);  
  4.         // TODO Auto-generated constructor stub
  5.     }  
  6.     @Override
  7.     publicvoid Handler(ProgramRequest request) {  
  8.         // TODO Auto-generated method stub
  9.         if(request.getType()==ProgramRequest.JAVA_QUESTION){  
  10.             System.out.println(m_name+"說: 你的問題是  "+"/""+request.getContent()+"/""+" 我來和你交流下吧");  
  11.         }  
  12.         elseif(getNextHandler()!=null){      
  13.             getNextHandler().Handler(request);  
  14.         }  
  15.         else{  
  16.             System.out.println("目前菜菜和貓貓都不知道這個問題呀!");  
  17.         }  
  18.     }  
  19. }  

在菜菜的的實現方法 Handler中(寫demo的時候,方法名寫錯了,不應該大寫的哦,大家注意一下),根據傳遞的request引數,來判斷是否是符合自己胃口的問題,如果是,則幫助濤濤處理;否則,像下一級傳遞,若有下一級,則重複之(遞迴麼?)。否則,濤濤的問題無法得到解決。

貓類:

Code:
  1. publicclass MiaoMiaoHandler extends IProgramHandler {  
  2.     public MiaoMiaoHandler(String name) {  
  3.         super(name);  
  4.         // TODO Auto-generated constructor stub
  5.     }  
  6.     @Override
  7.     publicvoid Handler(ProgramRequest request) {  
  8.         // TODO Auto-generated method stub
  9.         if(request.getType()==ProgramRequest.DOTNET_QUESTION){  
  10.             System.out.println(m_name+"說: 你的問題是 "+"/""+request.getContent()+"/""+" 我來和你交流下吧");  
  11.         }  
  12.         elseif(getNextHandler()!=null){      
  13.             getNextHandler().Handler(request);  
  14.         }  
  15.         else{  
  16.             System.out.println("目前菜菜和貓貓都不知道這個問題呀");  
  17.         }  
  18.     }  
  19. }  

和上面一個子類一樣,不多做解釋。

下面是測試程式碼:

Code:
  1. publicclass TaoTaoRequest {  
  2.  publicstaticvoid main(String args[]){  
  3.      IProgramHandler caicaiHandler = new CaiCaiHandler("菜菜");  
  4.      IProgramHandler miaomiaoHandler = new MiaoMiaoHandler("喵喵");  
  5.      ProgramRequest dotnetRequest =new ProgramRequest(1,"C#4.0是否支援了Duck Typing");  
  6.      ProgramRequest javaRequest =new ProgramRequest(2,"能否講解一下jvm的原理");  
  7.      caicaiHandler.setNextHandler(miaomiaoHandler);  
  8.      caicaiHandler.Handler(dotnetRequest);  
  9.  }  
  10. }  

測試結果為:

Code:
  1. 喵喵說: 你的問題是 "C#4.0是否支援了Duck Typing" 我來和你交流下吧  

這裡的測試程式碼,是將菜菜作為第一級接收者,貓作為第二級,濤濤現在問的問題是C#的,菜菜不懂,於是傳遞給貓,貓解決了。

看了上面的程式碼過程,相信大家會有和菜菜一樣的疑惑:這樣如果處理請求的人多了,而濤濤的問題沒人能解決,或者剛好最後一個人能解決,豈不是要一直遍歷到最後一步。產生了那麼多沒用的物件?  是的,職責鏈模式在這方面的確存在這樣的 問題,可能產生很多物件,而且是沒用的,導致記憶體的浪費。

但是,職責模式在一定程度上,給請求與處理兩者之間解耦,請求者無需知道一個請求具體由哪個物件進行處理,只是將這個請求傳遞給職責鏈(這裡,其實可以把菜菜這個類,當成一個鏈的首部),而且不同的處理者之間,只是通過一個引用進行關聯,除非處理請求的演算法改變,不然,這個模式很符合開閉原則。

下面開始講Command模式,這個模式,菜菜寫demo寫了好久哦。 主要想把Java的回撥方式也體現一下。 還是和以前一樣,第二個模式直接上程式碼啦。

場景:一隻會寫程式的貓,一個想讓貓改變寫程式狀態的主人。

Command介面:

Code:
  1. publicinterface ICommand {  
  2.     publicvoid excute();  
  3.     publicvoid undo();  
  4. }  

command模式一般接口裡面就是一個方法excute(),但是,由於命令模式經常被用來處理redo和undo,所以也會新增此類方法,菜菜這裡只演示簡單的undo(撤銷操作)。

具體實現類(這裡,如果不用回撥實現的話,是必須的,否則,可以在回撥方法中實現)

Code:
  1. publicclass CatCommand implements ICommand {  
  2.     private Cat cat = null;  
  3.     String currentState ;  
  4.     public CatCommand(Cat cat){  
  5.         this.cat = cat;  
  6.     }  
  7.     @Override
  8.     publicvoid excute() {  
  9.         // TODO Auto-generated method stub
  10.         currentState = cat.getState();  
  11.         System.out.println("貓的狀態:"+cat.getState());  
  12.         cat.setSleep();  
  13.         System.out.println("執行excute後貓的狀態:"+cat.getState());  
  14.     }  
  15.     @Override
  16.     publicvoid undo() {  
  17.         // TODO Auto-generated method stub
  18.         if(currentState.equals(Cat.ACTIVE)){  
  19.             cat.setActive();  
  20.              System.out.println("恢復貓的狀態:"+cat.getState());  
  21.         }  
  22.         elseif(currentState.equals(Cat.SLEEP)){  
  23.             cat.setSleep();  
  24.              System.out.println("恢復貓的狀態:"+cat.getState());  
  25.         }  
  26.         else{  
  27.              cat.setProg();       
  28.              System.out.println("恢復貓的狀態:"+cat.getState());  
  29.         }  
  30.     }  
  31. }  

說一個undo方法,因為每次執行excute的時候,都會把當前狀態儲存在currentState這個變數中,所以執行undo方法時,即根據儲存的狀態,執行cat的相應方法,即可以達到撤銷效果。 這裡做演示用,只是一個String的屬性,如果需要儲存複雜的物件,可以考慮使用備忘錄模式;如果需要儲存大量的物件,則需要用棧來儲存,然後一個個的執行undo。

上面程式碼中用到的cat類,用了餓漢單例模式:

Code:
  1. publicclass Cat {  
  2.  publicstaticfinal String SLEEP="睡覺";  
  3.  publicstaticfinal String ACTIVE="活蹦亂跳";  
  4.  publicstaticfinal String PROGRAMING="寫程式ing";  
  5.  private String catState;  
  6.  privatestatic Cat catInstance = new Cat();  
  7.  private Cat(){  
  8.      catState =PROGRAMING;  
  9.  }  
  10.  publicstatic Cat getInstance(){  
  11.      return catInstance;  
  12.  }  
  13.  publicvoid setSleep(){  
  14.      catState = SLEEP;  
  15.  }  
  16.  publicvoid setActive(){  
  17.      catState = ACTIVE;  
  18.  }  
  19.  publicvoid setProg(){  
  20.      catState = PROGRAMING;  
  21.  }  
  22.  public String getState(){  
  23.      return catState;  
  24.  }  
  25. }  

Invoker類,命令物件的使用者:

Code:
  1. publicclass Invoker {  
  2.     /** 
  3.      * 用於回撥方法 
  4.      */
  5.     private ICommand command =null;  
  6.     publicvoid excuteCommand(ICommand command){  
  7.      Cat cat = Cat.getInstance();  
  8.      System.out.println("貓的狀態:"+cat.getState());  
  9.      command.excute();  
  10.      System.out.println("執行excute後貓的狀態:"+cat.getState());  
  11.      command.undo();  
  12.      System.out.println("恢復貓的狀態:"+cat.getState());  
  13.  }  
  14.  /** 
  15.   * 用於非回撥方法 
  16.   */
  17.  publicvoid excuteCatCommand(){  
  18.      command.excute();  
  19.      command.undo();  
  20.  }  
  21.  publicvoid setCommand(ICommand cmd){  
  22.      this.command = cmd;  
  23.  }  
  24. }  

如果需要用回撥方法,那麼是不需要指定傳遞command介面的實現類物件的;如果不用回撥,只需要先呼叫setCommand方法,制定command的實現類物件,然後呼叫實現類物件的excute和undo方法。

客戶端呼叫類:

Code:
  1. publicclass Client {  
  2. publicstaticvoid main(String args[]){  
  3.     /** 
  4.      * 回撥方式實現 
  5.      */
  6. //  Invoker invoker = new Invoker();
  7. //  invoker.excuteCommand(new ICommand() {
  8. //      
  9. //      String currentState;
  10. //      Cat cat = Cat.getInstance();
  11. //      @Override
  12. //      public void excute() {
  13. //          // TODO Auto-generated method stub
  14. //          currentState =cat.getState();
  15. //          cat.setActive();
  16. //      }
  17. //
  18. //      @Override
  19. //      public void undo() {
  20. //          // TODO Auto-generated method stub
  21. //          if(currentState.equals(Cat.ACTIVE)){
  22. //              cat.setActive();
  23. //          }
  24. //          else if(currentState.equals(Cat.SLEEP)){
  25. //              cat.setSleep();
  26. //          }
  27. //          else{
  28. //              cat.setProg();      
  29. //          }
  30. //      }
  31. //  });
  32.     /** 
  33.      * 非回撥方式 
  34.      */
  35.     Invoker  invoker = new Invoker();  
  36.     ICommand catCommand = new CatCommand(Cat.getInstance());  
  37.     invoker.setCommand(catCommand);  
  38.     invoker.excuteCatCommand();  
  39. }  
  40. }  

註釋程式碼為Java的回撥實現,如果用這個方式,上面的CatCommand類就不需要,invoker類中,也可以直接執行第一個方法。

(順帶著扯一下,java中的回撥機制,是靠傳遞介面引用來實現,和C不一樣的哦)。

測試結果為:

Code:
  1. 貓的狀態:寫程式ing  
  2. 執行excute後貓的狀態:睡覺  
  3. 恢復貓的狀態:寫程式ing  

好了。又寫了篇文章,這段時間只發表了一個學習python的小感觸,發現自己寫文章又犯糊塗了,如果大家看著覺得迷糊的話,見諒啊,可以和菜選單獨交流。 歡迎交流,拍磚。