命令模式:
定義:把一個請求或者操作封裝在命令對象中。命令模式允許系統使用不同的請求把客戶端參數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。
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框架示意圖:
說明:
- 視圖層採用JSP實現
- 控制器採用Servlet實現,整個框架採用同一個Servlet,以實現請求的中轉
- 模型層採用Java實現,主要決定用來做什麼
- 在模型層後添加了一個DAO,目的是將決定做什麼和具體怎麼做分開
整個Web框架大致的流程是:首先客戶端發送請求,提交JSP頁面給中轉器(Servlet);中轉器根據客戶的請求,選擇相應的模型層,即Logic,Logic進行相應的邏輯處理;如果需要使用數據庫,則通過DAO進行相應的數據庫操作。
下面主要看一下控制層和模型層的設計,應用命令模式:
1.控制層設計
控制層主要用來轉發從視圖層傳來的數據和請求到相對應的模型層,因此,實現它最好的方式莫過於使用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)是用來處理視圖層發送來的請求的方法。
如果直接使用request方式來獲取從頁面提交的數據,在要獲取的數據比較多的情況下,會比較煩瑣,而且直接將request傳遞給模型層不符合Model 2規範。所以,這裏將對從頁面傳來的值進行封裝,將其放在一個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的類的名稱和所在的包。示例代碼如下:
- private String getActionName(String systemName ,String actionName)
- throws IOException, Exception {
- return "com. " + systemName + ".action." + actionName;
- }
使用RequestDispatcher返回視圖層。示例代碼如下:
- req.setAttribute("infoOut", infoOut);
- RequestDispatcher rd = req.getRequestDispatcher("/"+ systemName +
- "/jsp/" + forwardJsp+ ".jsp");
- rd.forward(req, res);
代碼說明:
- 這裏表示JSP文件放在項目中系統名下的jsp文件夾下。
2.模型層設計
假定有一個模型層類爲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.使用命令模式會導致某些系統有過多的具體命令類。某些系統可能需要幾十個,幾百個甚至幾千個具體命令類,這會使命令模式在這樣的系統裏變得不實際。
適用性:
在下面的情況下應當考慮使用命令模式:
1.使用命令模式作爲"CallBack"在面向對象系統中的替代。"CallBack"講的便是先將一個函數登記上,然後在以後調用此函數。
2. 需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命期。換言之,原先的請求發出者可能已經不在了,而命令對象本身仍 然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在串形化之後傳送到另外一臺機器上去。
3.系統需要支持命令的撤消(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供redo()方法,以供客戶端在需要時,再重新實施命令效果。
4.如果一個系統要將系統中所有的數據更新到日誌裏,以便在系統崩潰時,可以根據日誌裏讀回所有的數據更新命令,重新調用Execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。
參考:
1.《自己動手寫Struts:構建基於MVC的Web開發框架》
2.《敏捷軟件開發:原則、模式與實踐》
3.《Head First設計模式》