一、引言
忙里偷閑,終于動筆了。命令模式是從界面設計中提取出來的一種分離耦合,提高重用的方法。被認為是最優雅而且簡單的模式,它的應用范圍非常廣泛。讓我們一起來認識下它吧。
先從起源說起。在設計界面時,大家可以注意到這樣的一種情況,同樣的菜單控件,在不同的應用環境中的功能是完全不同的;而菜單選項的某個功能可能和鼠標右鍵的某個功能完全一致。按照最差、最原始的設計,這些不同功能的菜單、或者右鍵彈出菜單是要分開來實現的,你可以想象一下,word文檔上面的一排菜單要實現出多少個“形似神非”的菜單類來?這完全是行不通的。這時,就要運用分離變化與不變的因素,將菜單觸發的功能分離出來,而制作菜單的時候只是提供一個統一的觸發接口。這樣修改設計后,功能點可以被不同的菜單或者右鍵重用;而且菜單控件也可以去除變化因素,很大的提高了重用;而且分離了顯示邏輯和業務邏輯的耦合。這便是命令模式的雛形。
下面我們將仔細的討論下命令模式。
二、定義與結構
《設計模式》中命令模式的定義為:將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤消的操作。
看起來,命令模式好像神通廣大。其實命令模式的以上功能還要看你是怎么寫的——程序總是程序員寫出來的,你寫啥它才能干啥:)
在我看來,其實命令模式像很多設計模式一樣——通過在你的請求和處理之間加上了一個中間人的角色,來達到分離耦合的目的。通過對中間人角色的特殊設計來形成不同的模式。當然命令模式就是一種特殊設計的結果。
看下命令模式是有哪些角色來組成的吧。
1) 命令角色(Command):聲明執行操作的接口。有java接口或者抽象類來實現。
2) 具體命令角色(Concrete Command):將一個接收者對象綁定于一個動作;調用接收者相應的操作,以實現命令角色聲明的執行操作的接口。
3) 客戶角色(Client):創建一個具體命令對象(并可以設定它的接收者)。
4) 請求者角色(Invoker):調用命令對象執行這個請求。
5) 接收者角色(Receiver):知道如何實施與執行一個請求相關的操作。任何類都可能作為一個接收者。
以下是命令模式的類圖,從中可以大致的了解到各個角色之間是怎么來協調工作的。
三、舉例
本來想接著我的JUnit分析來講解命令模式。但是由于在JUnit中,參雜了其它的模式在里面,使得命令模式的特點不太明顯。所以這里將以命令模式在Web開發中最常見的應用——Struts中Action的使用作為例子。
在Struts中Action控制類是整個框架的核心,它連接著頁面請求和后臺業務邏輯處理。按照框架設計,每一個繼承自Action的子類,都實現execute方法——調用后臺真正處理業務的對象來完成任務。
注:繼承自DispatchAction的子類,則可以一個類里面處理多個類似的操作。這個在這不做討論。
下面我們將Struts中的各個類與命令模式中的角色對號入座。
先來看下命令角色——Action控制類
public class Action {
……
/*
*可以看出,Action中提供了兩個版本的執行接口,而且實現了默認的空實現。
*/
public ActionForward execute( ActionMapping mapping,
ActionForm form,
ServletRequest request,
ServletResponse response)
throws Exception {
try {
return execute(mapping, form, (HttpServletRequest) request,
(HttpServletResponse) response);
} catch (ClassCastException e) {
return null;
}
}
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return null;
}
}
下面的就是請求者角色,它僅僅負責調用命令角色執行操作。
public class RequestProcessor {
……
protected ActionForward processActionPerform(HttpServletRequest request,
HttpServletResponse response,
Action action,
ActionForm form,
ActionMapping mapping)
throws IOException, ServletException {
try {
return (action.execute(mapping, form, request, response));
} catch (Exception e) {
return (processException(request, response,e, form, mapping));
}
}
}
Struts框架為我們提供了以上兩個角色,要使用struts框架完成自己的業務邏輯,剩下的三個角色就要由我們自己來實現了。步驟如下:
1) 很明顯我們要先實現一個Action的子類,并重寫execute方法。在此方法中調用業務模塊的相應對象來完成任務。
2) 實現處理業務的業務類。
3) 配置struts-config.xml配置文件,將自己的Action和Form以及相應頁面結合起來。
4) 編寫jsp,在頁面中顯式的制定對應的處理Action。
一個完整的命令模式就介紹完了。當你在頁面上提交請求后,Struts框架會根據配置文件中的定義,將你的Action對象作為參數傳遞給RequestProcessor類中的processActionPerform()方法,由此方法調用Action對象中的執行方法,進而調用業務層中的接收角色。這樣就完成了請求的處理。
四、Undo、事務及延伸
在定義中提到,命令模式支持可撤銷的操作。而在上面的舉例中并沒有體現出來。其實命令模式之所以能夠支持這種操作,完全得益于在請求者與接收者之間添加了中間角色。為了實現undo功能,首先需要一個歷史列表來保存已經執行過的具體命令角色對象;修改具體命令角色中的執行方法,使它記錄更多的執行細節,并將自己放入歷史列表中;并在具體命令角色中添加undo方法,此方法根據記錄的執行細節來復原狀態(很明顯,首先程序員要清楚怎么來實現,因為它和execute的效果是一樣的)。
同樣,redo功能也能夠照此實現。
命令模式還有一個常見的用法就是執行事務操作。這就是為什么命令模式還叫做事務模式的原因吧。它可以在請求被傳遞到接收者角色之前,檢驗請求的正確性,甚至可以檢查和數據庫中數據的一致性,而且可以結合組合模式的結構,來一次執行多個命令。
使用命令模式不僅僅可以解除請求者和接收者之間的耦合,而且可以用來做批處理操作,這完全可以發揮你自己的想象——請求者發出的請求到達命令角色這里以后,先保存在一個列表中而不執行;等到一定的業務需要時,命令模式再將列表中全部的操作逐一執行。
哦,命令模式實在太靈活了。真是一個很有用的東西啊!
五、優點及適用情況
由上面的講解可以看出命令模式有以下優點:
1) 命令模式將調用操作的請求對象與知道如何實現該操作的接收對象解耦。
2) 具體命令角色可以被不同的請求者角色重用。
3) 你可將多個命令裝配成一個復合命令。
4) 增加新的具體命令角色很容易,因為這無需改變已有的類。
GOF總結了命令模式的以下適用環境。
1) 需要抽象出待執行的動作,然后以參數的形式提供出來——類似于過程設計中的回調機制。而命令模式正是回調機制的一個面向對象的替代品。
2) 在不同的時刻指定、排列和執行請求。一個命令對象可以有與初始請求無關的生存期。
3) 需要支持取消操作。
4) 支持修改日志功能。這樣當系統崩潰時,這些修改可以被重做一遍。
5) 需要支持事務操作。
六、總結
命令模式是一個很有用的模式,希望這篇文章能給你實質性的幫助。謝謝大家指正。 v