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