學習:Java設計模式—Comman:1

命令模式:

定義:把一個請求或者操作封裝在命令對象中。命令模式允許系統使用不同的請求把客戶端參數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。

7d8e0742-c5d2-30aa-9e98-2922efcbc1fb

Invoker類 被客戶端調用,可以接受命令請求,設計命令隊列,決定是否相應該請求,記錄或撤銷或重做命令請求,記錄日誌等等.

    1. public class Invoker {  
    2. private Command command;  
    3. public void setOrder(Command command) {  
    4. this.command = command;  
    5.     }  
    6. public void ExecuteCommand() {  
    7.         command.ExecuteCommand();  
    8.     }  
    9. }  

Command類,將一個請求封裝成一個對象,將一個請求具體化,方便對請求記錄。

    1. public abstract class Command {  
    2. protected Receiver receiver;  
    3. public Command(Receiver receiver){  
    4. this.receiver = receiver;  
    5.     }  
    6. public abstract void ExecuteCommand();  
    7. }  

ConcreteCommand類,可以將Receiver對象放到這個類裏面,這個類具體實現了要怎麼處理這個用戶的請求。

    1. public class ConcreteCommand extends Command {  
    2. public ConcreteCommand(Receiver receiver){  
    3. super(receiver);  
    4.     }  
    5. @Override 
    6. public void ExecuteCommand() {  
    7.         receiver.Execute();  
    8.     }  
    9. }  

Receiver類,其實這個類可以沒有,不過爲了讓設計看起來更整潔清楚。

    1. public class Receiver {  
    2. public void Execute(){  
    3.         System.out.println("Receiver excute!");  
    4.     }  
    5. }  

最後一個Client類。

    1. public class Client {  
    2. public static void main(String[] args) {  
    3.         Receiver r = new Receiver();  
    4.         Command c = new ConcreteCommand(r);   
    5.         Invoker i = new Invoker();  
    6.         i.setOrder(c);  
    7.         i.ExecuteCommand();  
    8.     }  
    9. }  

命令模式在MVC中的應用:

      Struts中,在模型層都要繼承一個Action接口,並實現execute方法,其實這個Action就是命令類。爲什麼Struts會應用命令模 式,是因爲Struts的核心控制器ActionServlet只有一個,相當於Invoker,而模型層的類會隨着不同的應用有不同的模型類,相當於具 體的Command。這樣,就需要在ActionServlet和模型層之間解耦,而命令模式正好解決這個問題。

      MVC Model2 實現的Web框架示意圖:

image002

說明:

  • 視圖層採用JSP實現
  • 控制器採用Servlet實現,整個框架採用同一個Servlet,以實現請求的中轉
  • 模型層採用Java實現,主要決定用來做什麼
  • 在模型層後添加了一個DAO,目的是將決定做什麼和具體怎麼做分開

      整個Web框架大致的流程是:首先客戶端發送請求,提交JSP頁面給中轉器(Servlet);中轉器根據客戶的請求,選擇相應的模型層,即Logic,Logic進行相應的邏輯處理;如果需要使用數據庫,則通過DAO進行相應的數據庫操作。

下面主要看一下控制層和模型層的設計,應用命令模式:

1.控制層設計

      控制層主要用來轉發從視圖層傳來的數據和請求到相對應的模型層,因此,實現它最好的方式莫過於使用Servlet了。當從視圖層獲取請求後,首先通過對web.xml文件的配置,使其轉入Servlet,在Servlet中完成對頁面中數據的封裝和對相應模型的選擇,然後再到相應的模型層進行數據處理; 當在模型層數據處理完畢後,通過RequestDispatcher將處理後的數據返回相應的視圖頁面。

      在Servlet中,將使用doPost()來處理相應的中轉請求,如果開發人員使用get提交方式,則使用如下方式進行處理。示例代碼如下:

  1. public void doGet(HttpServletRequest req, HttpServletResponse res) 
  2. throws ServletException, IOException { 
  3.     doPost(req, res); 
  4. //使用post提交方式 
  5. public void doPost(HttpServletRequest req, HttpServletResponse res) 
  6. throws ServletException, IOException { 
  7.     do_Dispatcher (req, res); 

代碼說明:

  • 不論採用get還是post提交方式,都將執行do_Dispatcher(req, res)方法。
  • do_Dispatcher(req, res)是用來處理視圖層發送來的請求的方法。

      如果直接使用request方式來獲取從頁面提交的數據,在要獲取的數據比較多的情況下,會比較煩瑣,而且直接將request傳遞給模型層不符合Model  2規範。所以,這裏將對從頁面傳來的值進行封裝,將其放在一個Map中,然後再傳遞給模型層,這樣在模型層就可以直接使用Map中的值。示例代碼如下:

  1. private HashMap getRequestToMap(HttpServletRequest req) throws Exception { 
  2.     req.setCharacterEncoding("GBK"); 
  3.         HashMap infoIn = new HashMap(); 
  4.         for (Enumeration e = req.getParameterNames(); e.hasMoreElements ();) 
  5.                 {//獲取頁面中所有元素名 
  6.             String strName = (String)e.nextElement(); 
  7.             String[] values = (String[]) req.getParameterValues (strName); 
  8.                 //根據名稱獲取對應的值 
  9.             if (values == null) {//假如沒有值 
  10.                 infoIn.put(strName, ""); 
  11.             } else if (values.length == 1) {//假如只有一個值 
  12.                 infoIn.put(strName, values[0]); 
  13.             } else {//假如有多個值 
  14.                 infoIn.put(strName, values); 
  15.             } 
  16.         } 
  17.     return infoIn; 

代碼說明:

  • req.setCharacterEncoding("GBK"),這裏首先將從視圖層傳來的數據設定編碼爲GBK。
  • HashMap infoIn = new HashMap(),定義一個HashMap,用來存放從request中獲取的數據。
  • req.getParameterNames(),用來獲取從頁面中傳來的所有元素。
  • req.getParameterValues(),用來根據元素名稱來獲取元素對應的值,並將元素名稱和值的對應關係存入HashMap中。如果元素的值爲空,則在HashMap中將元素名稱對應的值置爲空;如果只有一個值,則將該值存入;如果有多個值,則存入數組。

命令模式使用:

一個視圖對應一個模型,也可能一個視圖對應多個模型,但只有一個控制器,所以,爲了實現一個控制器可以轉發到多個模型中去,就需要使用接口,讓所有 模型都實現這個接口,然後在控制器裏,僅僅是面對接口編程即可。這裏定義一個接口Action.java,Action.java的示例代碼如下:

  1. //******* Action.java************** 
  2. import java.util.*; 
  3. public interface Action{ 
  4.     public HashMap doAction(HashMap infoIn); 

在控制器中只針對這個接口處理即可。示例代碼如下:

  1. Actionaction = (Action) Class.forName(getActionName(systemName, 
  2. logicName)).newInstance(); 
  3. HashMap infoOut = action.doAction(infoIn); 

代碼說明:

  • getActionName()方法是獲取實現接口Action的類的名稱和所在的包。示例代碼如下:
  1. private String getActionName(String systemName ,String actionName) 
  2. throws IOException, Exception { 
  3. return "com. " + systemName + ".action." + actionName; 

使用RequestDispatcher返回視圖層。示例代碼如下:

  1. req.setAttribute("infoOut", infoOut); 
  2. RequestDispatcher rd = req.getRequestDispatcher("/"+ systemName + 
  3. "/jsp/" + forwardJsp+ ".jsp"); 
  4. rd.forward(req, res); 

代碼說明:

  • 這裏表示JSP文件放在項目中系統名下的jsp文件夾下。

2.模型層設計

假定有一個模型層類爲WebExamAction.java,主要用來負責在線考試系統的業務處理,則這個類要實現Action接口。示例代碼如下:

  1. //******* WebExamAction.java************** 
  2. import java.util.HashMap; 
  3. public class WebExamAction implements Action{ 
  4.     //根據頁面的請求,進行動作的轉換 
  5.     public HashMap doAction(HashMap infoIn) { 
  6.         String action = (infoIn.get("action") == null) ? ""
  7. (String)infoIn.get("action"); 
  8.         HashMap infoOut = new HashMap(); 
  9.         if (action.equals(""))           infoOut = this.doInit (infoIn); 
  10.         else if (action.equals("insert")) infoOut = this.doInsert (infoIn); 
  11.         return infoOut; 
  12.     } 
  13.     /**該方法設置用戶登錄時頁面的初始信息 
  14.     * @param infoIn 
  15.     * @return HashMap 
  16.     */ 
  17.     private HashMap doInit(HashMap infoIn) { 
  18.         HashMap infoOut = infoIn; 
  19.         int clerkId = (infoIn.get("clerkId") == null || "".equals 
  20. (infoIn.get("clerkId"))) ? -1 : Integer.parseInt((String) 
  21. infoIn.get ("clerkId")); 
  22.         try
  23.             //取得考生姓名 
  24.             Users user = new Users(); 
  25.             String clerkName = user.getName(clerkId);  
  26.             infoOut.put("clerkName", clerkName); 
  27.         } catch(Exception e) { 
  28.             e.printStackTrace(); 
  29.         } finally
  30.             return infoOut; 
  31.         } 
  32.     } 
  33.     /**該方法用來進行新增
  34.     * @param infoIn
  35.     * @return HashMap
  36.     */ 
  37.     private HashMap doInsert(HashMap infoIn) { 
  38.         HashMap infoOut = infoIn; 
  39.         int clerkId = (infoIn.get("clerkId") == null || "".equals 
  40. (infoIn.get("clerkId"))) ? -1 : Integer.parseInt((String) 
  41. infoIn.get ("clerkId")); 
  42.         try
  43.             //取得考生姓名 
  44.             Users user = new Users(); 
  45.             String clerkName = user.getName(clerkId);  
  46.             infoOut.put("clerkName", clerkName); 
  47.         } catch(Exception e) { 
  48.             e.printStackTrace(); 
  49.         } finally
  50.             return infoOut; 
  51.         } 
  52.     } 

代碼說明:

  • 這裏,在doAction中根據從頁面傳來的action進行動作請求的轉換。
  • 通過一個名爲infoIn的HashMap,來負責傳入頁面中元素的值。
  • 通過一個名爲infoOut的HashMap,來負責將處理後的數據傳出。

可以看出,如果模型層都實現接口Action,實現doAction方法,即可實現動作請求的轉換。

命令模式要點:

1.Command模式的根本目的在於將“行爲請求者”與“行爲實現者”解耦,在面嚮對象語言中,常見的實現手段是“將行爲抽象爲對象”。  
2.實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。   
3.通過使用Compmosite模式,可以將多個命令封裝爲一個“複合命令”MacroCommand。   
4.Command模式與C#中的Delegate有些類似。但兩者定義行爲接口的規範有所區別:Command以面向對象中的“接口-實現”來定義行爲接口規範,更嚴格, 更符合抽象原則;Delegate以函數簽名來定義行爲接口規範,更靈活,但抽象能力比較弱。   
5.使用命令模式會導致某些系統有過多的具體命令類。某些系統可能需要幾十個,幾百個甚至幾千個具體命令類,這會使命令模式在這樣的系統裏變得不實際。

適用性:    

    在下面的情況下應當考慮使用命令模式:   
1.使用命令模式作爲"CallBack"在面向對象系統中的替代。"CallBack"講的便是先將一個函數登記上,然後在以後調用此函數。   
2. 需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命期。換言之,原先的請求發出者可能已經不在了,而命令對象本身仍 然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在串形化之後傳送到另外一臺機器上去。   
3.系統需要支持命令的撤消(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供redo()方法,以供客戶端在需要時,再重新實施命令效果。   
4.如果一個系統要將系統中所有的數據更新到日誌裏,以便在系統崩潰時,可以根據日誌裏讀回所有的數據更新命令,重新調用Execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。

參考:

1.《自己動手寫Struts:構建基於MVC的Web開發框架》

2.《敏捷軟件開發:原則、模式與實踐》

3.《Head First設計模式》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章