標籤:mvc 設計模式
其實在項目開發中,我們使用了大量的設計模式,只是這些設計模式都封裝在架構中了,如果你想要不僅僅局限於簡單的使用,就應該深入瞭解架構的設計思路。
在MVC架構中,模式之一就是命令模式,先來看看模式是如何定義的。
命令模式:
定義:把一個請求或者操作封裝在命令對象中。命令模式允許系統使用不同的請求把用戶端參數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢複功能。
Invoker類被用戶端調用,可以接受命令請求,設計命令隊列,決定是否相應該請求,記錄或撤銷或重做命令請求,記錄日誌等等.
public class Invoker { private Command command; public void setOrder(Command command) { this.command = command; } public void ExecuteCommand() { command.ExecuteCommand(); } }
Command類,將一個請求封裝成一個對象,將一個請求具體化,方便對請求記錄。
public abstract class Command { protected Receiver receiver; public Command(Receiver receiver){ this.receiver = receiver; } public abstract void ExecuteCommand(); } ConcreteCommand類,可以將Receiver對象放到這個類裡面,這個類具體實現了要怎麼處理這個使用者的請求。public class ConcreteCommand extends Command { public ConcreteCommand(Receiver receiver){ super(receiver); } @Override public void ExecuteCommand() { receiver.Execute(); } }
Receiver類,其實這個類可以沒有,不過為了讓設計看起來更整潔清楚。
public class Receiver { public void Execute(){ System.out.println("Receiver excute!"); } }
最後一個Client類。
public class Client { public static void main(String[] args) { Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoker i = new Invoker(); i.setOrder(c); i.ExecuteCommand(); } }
命令模式在MVC中的應用:
Struts中,在模型層都要繼承一個Action介面,並實現execute方法,其實這個Action就是命令類。為什麼Struts會應用程式命令模式,是因為Struts的核心控制器ActionServlet只有一個,相當於Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當於具體的Command。這樣,就需要在ActionServlet和模型層之間解耦,而命令模式正好解決這個問題。
MVC Model2實現的Web架構:
說明:
控制器採用Servlet實現,整個架構採用同一個Servlet,以實現請求的中轉
Controller Servlet包括兩部分:處理常式和命令
整個Web架構大致的流程是:首先用戶端發送請求,提交JSP頁面給中轉器(Servlet);中轉器根據客戶的請求,選擇相應的模型層,即Logic,Logic進行相應的邏輯處理;如果需要使用資料庫,則通過DAO進行相應的資料庫操作。
下面主要看一下控制層和模型層的設計,應用程式命令模式:
控制層設計
控制層主要用來轉寄從視圖層傳來的資料和請求到相對應的模型層,因此,實現它最好的方式莫過於使用Servlet了。當從視圖層擷取請求後,首先通過對web.xml檔案的配置,使其轉入Servlet,在Servlet中完成對頁面中資料的封裝和對相應模型的選擇,然後再到相應的模型層進行資料處理;當在模型層資料處理完畢後,通過RequestDispatcher將處理後的資料返回相應的視圖頁面。
在Servlet中,將使用doPost()來處理相應的中轉請求,如果開發人員使用get提交方式,則使用如下方式進行處理。範例程式碼如下:
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } //使用post提交方式 public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { do_Dispatcher (req, res); }
代碼說明:
不論採用get還是post提交方式,都將執行do_Dispatcher(req, res)方法。
do_Dispatcher(req, res)是用來處理視圖層發送來的請求的方法。
這樣的做體現了Front Controller模式,如果直接使用request方式來擷取從頁面提交的資料,在要擷取的資料比較多的情況下,會比較煩瑣,而且直接將request傳遞給模型層不符合Model2規範。所以,這裡將對從頁面傳來的值進行封裝,將其放在一個Map中,然後再傳遞給模型層,這樣在模型層就可以直接使用Map中的值。範例程式碼如下:
private HashMap getRequestToMap(HttpServletRequest req) throws Exception { req.setCharacterEncoding("GBK"); HashMap infoIn = new HashMap(); for (Enumeration e = req.getParameterNames(); e.hasMoreElements ();) {//擷取頁面中所有元素名 String strName = (String)e.nextElement(); String[] values = (String[]) req.getParameterValues (strName); //根據名稱擷取對應的值 if (values == null) {//假如沒有值 infoIn.put(strName, ""); } else if (values.length == 1) {//假如只有一個值 infoIn.put(strName, values[0]); } else {//假如有多個值 infoIn.put(strName, values); } } return infoIn; }
代碼說明:
req.setCharacterEncoding("GBK"),這裡首先將從視圖層傳來的資料設定編碼為GBK。
HashMap infoIn = new HashMap(),定義一個HashMap,用來存放從request中擷取的資料。
req.getParameterNames(),用來擷取從頁面中傳來的所有元素。
req.getParameterValues(),用來根據元素名稱來擷取元素對應的值,並將元素名稱和值的對應關係存入HashMap中。如果元素的值為空白,則在HashMap中將元素名稱對應的值置為空白;如果只有一個值,則將該值存入;如果有多個值,則存入數組。
命令模式使用:
一個視圖對應一個模型,也可能一個視圖對應多個模型,但只有一個控制器,所以,為了實現一個控制器可以轉寄到多個模型中去,就需要使用介面,讓所有模型都實現這個介面,然後在控制器裡,僅僅是面對介面編程即可。
這裡定義一個介面Action.java,Action.java的範例程式碼如下:
//******* Action.java************** import java.util.*; public interface Action{ public HashMap doAction(HashMap infoIn); }
在控制器中只針對這個介面處理即可。範例程式碼如下:
Actionaction = (Action) Class.forName(getActionName(systemName, logicName)).newInstance(); HashMap infoOut = action.doAction(infoIn);
代碼說明:
getActionName()方法是擷取實現介面Action的類的名稱和所在的包。
使用RequestDispatcher返回視圖層。範例程式碼如下:
req.setAttribute("infoOut", infoOut); RequestDispatcher rd = req.getRequestDispatcher("/"+ systemName + "/jsp/" + forwardJsp+ ".jsp"); rd.forward(req, res);
代碼說明:
這裡表示JSP檔案放在項目中系統名下的jsp檔案夾下。
模型層設計
假定有一個模型層類為WebExamAction.java,主要用來負責線上考試系統的業務處理,則這個類要實現Action介面。範例程式碼如下:
//******* WebExamAction.java************** import java.util.HashMap; public class WebExamAction implements Action{ //根據頁面的請求,進行動作的轉換 public HashMap doAction(HashMap infoIn) { String action = (infoIn.get("action") == null) ? "" : (String)infoIn.get("action"); HashMap infoOut = new HashMap(); if (action.equals("")) infoOut = this.doInit (infoIn); else if (action.equals("insert")) infoOut = this.doInsert (infoIn); return infoOut; } /**該方法設定使用者登入時頁面的初始資訊 * @param infoIn * @return HashMap */ private HashMap doInit(HashMap infoIn) { HashMap infoOut = infoIn; int clerkId = (infoIn.get("clerkId") == null || "".equals (infoIn.get("clerkId"))) ? -1 : Integer.parseInt((String) infoIn.get ("clerkId")); try { //取得考生姓名 Users user = new Users(); String clerkName = user.getName(clerkId); infoOut.put("clerkName", clerkName); } catch(Exception e) { e.printStackTrace(); } finally { return infoOut; } } /**該方法用來進行新增 * @param infoIn * @return HashMap */ private HashMap doInsert(HashMap infoIn) { HashMap infoOut = infoIn; int clerkId = (infoIn.get("clerkId") == null || "".equals (infoIn.get("clerkId"))) ? -1 : Integer.parseInt((String) infoIn.get ("clerkId")); try { //取得考生姓名 Users user = new Users(); String clerkName = user.getName(clerkId); infoOut.put("clerkName", clerkName); } catch(Exception e) { e.printStackTrace(); } finally { return infoOut; } } }
代碼說明:
這裡,在doAction中根據從頁面傳來的action進行動作請求的轉換。
通過一個名為infoIn的HashMap,來負責傳入頁面中元素的值。
通過一個名為infoOut的HashMap,來負責將處理後的資料傳出。
可以看出,如果模型層都實現介面Action,實現doAction方法,即可實現動作請求的轉換。
命令模式要點:
1.Command模式的根本目的在於將“行為要求者”與“行為實現者”解耦,在物件導向語言中,常見的實現手段是“將行為抽象為對象”。
2.實現Command介面的具體命令對象ConcreteCommand有時候根據需要可能會儲存一些額外的狀態資訊。
3.通過使用Compmosite模式,可以將多個命令封裝為一個“複合命令”MacroCommand。
4.Command模式與C#中的Delegate有些類似。但兩者定義行為介面的規範有所區別:Command以物件導向中的“介面-實現”來定義行為介面規範,更嚴格,更符合抽象原則;Delegate以函數簽名來定義行為介面規範,更靈活,但抽象能力比較弱。
5.使用命令模式會導致某些系統有過多的具體命令類。某些系統可能需要幾十個,幾百個甚至幾千個具體命令類,這會使命令模式在這樣的系統裡變得不實際。