3.5 隊(duì)列請(qǐng)求##
所謂隊(duì)列請(qǐng)求,就是對(duì)命令對(duì)象進(jìn)行排隊(duì),組成工作隊(duì)列,然后依次取出命令對(duì)象來執(zhí)行
。多用多線程或者線程池來進(jìn)行命令隊(duì)列的處理,當(dāng)然也可以不用多線程,就是一個(gè)線程,一個(gè)命令一個(gè)命令的循環(huán)處理,就是慢點(diǎn)。
繼續(xù)宏命令的例子,其實(shí)在后廚,會(huì)收到很多很多的菜單,一般是按照菜單傳遞到后廚的先后順序來進(jìn)行處理,對(duì)每張菜單,假定也是按照菜品的先后順序進(jìn)行制作,那么在后廚就自然形成了一個(gè)菜品的隊(duì)列,也就是很多個(gè)用戶的命令對(duì)象的隊(duì)列。
后廚有很多廚師,每個(gè)廚師都從這個(gè)命令隊(duì)列里面取出一個(gè)命令,然后按照命令做出菜來,就相當(dāng)于多個(gè)線程在同時(shí)處理一個(gè)隊(duì)列請(qǐng)求。
因此后廚就是一個(gè)很典型的隊(duì)列請(qǐng)求的例子。
提示一點(diǎn):后廚的廚師與命令隊(duì)列之間是沒有任何關(guān)聯(lián)的,也就是說是完全解耦的
。命令隊(duì)列是客戶發(fā)出的命令,廚師只是負(fù)責(zé)從隊(duì)列里面取出一個(gè),處理,然后再取下一個(gè),再處理,僅此而已,廚師不知道也不管客戶是誰。
- 如何實(shí)現(xiàn)命令模式的隊(duì)列請(qǐng)求
(1)先從命令接口開始,除了execute方法外,新加了一個(gè)返回發(fā)出命令的桌號(hào),就是點(diǎn)菜的桌號(hào),還有一個(gè)是為命令對(duì)象設(shè)置接收者的方法,也把它添加到接口上,這個(gè)是為了后面多線程處理的時(shí)候方便使用。示例代碼如下:
/**
* 命令接口,聲明執(zhí)行的操作
*/
public interface Command {
/**
* 執(zhí)行命令對(duì)應(yīng)的操作
*/
public void execute();
/**
* 設(shè)置命令的接收者
* @param cookApi 命令的接收者
*/
public void setCookApi(CookApi cookApi);
/**
* 返回發(fā)起請(qǐng)求的桌號(hào),就是點(diǎn)菜的桌號(hào)
* @return 發(fā)起請(qǐng)求的桌號(hào)
*/
public int getTableNum();
}
(2)廚師的接口也發(fā)生了一點(diǎn)變化,在cook的方法上添加了發(fā)出命令的桌號(hào),這樣在多線程輸出信息的時(shí)候,才知道到底是在給哪個(gè)桌做菜,示例代碼如下:
/**
* 廚師的接口
*/
public interface CookApi {
/**
* 示意,做菜的方法
* @param tableNum 點(diǎn)菜的桌號(hào)
* @param name 菜名
*/
public void cook(int tableNum,String name);
}
** (3)開始來實(shí)現(xiàn)命令接口,為了簡單,這次只有熱菜,因?yàn)橐龉ぷ鞫荚诤髲N的命令隊(duì)列里面,因此涼菜就不要了,示例代碼如下:**
/**
* 命令對(duì)象,綠豆排骨煲
*/
public class ChopCommand implements Command{
/**
* 持有具體做菜的廚師的對(duì)象
*/
private CookApi cookApi = null;
/**
* 設(shè)置具體做菜的廚師的對(duì)象
* @param cookApi 具體做菜的廚師的對(duì)象
*/
public void setCookApi(CookApi cookApi) {
this.cookApi = cookApi;
}
/**
* 點(diǎn)菜的桌號(hào)
*/
private int tableNum;
/**
* 構(gòu)造方法,傳入點(diǎn)菜的桌號(hào)
* @param tableNum 點(diǎn)菜的桌號(hào)
*/
public ChopCommand(int tableNum){
this.tableNum = tableNum;
}
public int getTableNum(){
return this.tableNum;
}
public void execute() {
this.cookApi.cook(tableNum,"綠豆排骨煲");
}
}
(4)接下來構(gòu)建很重要的命令對(duì)象的隊(duì)列,其實(shí)也不是有多難,多個(gè)命令對(duì)象嘛,用個(gè)集合來存儲(chǔ)就好了,然后按照放入的順序,先進(jìn)先出即可。
/**
* 命令隊(duì)列類
*/
public class CommandQueue {
/**
* 用來存儲(chǔ)命令對(duì)象的隊(duì)列
*/
private static List<Command> cmds = new ArrayList<Command>();
/**
* 服務(wù)員傳過來一個(gè)新的菜單,需要同步,
* 因?yàn)橥瑫r(shí)會(huì)有很多的服務(wù)員傳入菜單,而同時(shí)又有很多廚師在從隊(duì)列里取值
* @param menu 傳入的菜單
*/
public synchronized static void addMenu(MenuCommand menu){
//一個(gè)菜單對(duì)象包含很多命令對(duì)象
for(Command cmd : menu.getCommands()){
cmds.add(cmd);
}
}
/**
* 廚師從命令隊(duì)列里面獲取命令對(duì)象進(jìn)行處理,也是需要同步的
*/
public synchronized static Command getOneCommand(){
Command cmd = null;
if(cmds.size() > 0 ){
//取出隊(duì)列的第一個(gè),因?yàn)槭羌s定的按照加入的先后來處理
cmd = cmds.get(0);
//同時(shí)從隊(duì)列里面取掉這個(gè)命令對(duì)象
cmds.remove(0);
}
return cmd;
}
}
提示:這里并沒有考慮一些復(fù)雜的情況,比如:如果命令隊(duì)列里面沒有命令,而廚師又來獲取命令怎么辦?
這里只是做一個(gè)基本的示范,并不是完整的實(shí)現(xiàn),所以這里就沒有去處理這些問題了,當(dāng)然出現(xiàn)這種問題,就需要使用wait/notify來進(jìn)行線程調(diào)度了
。
(5)有了命令隊(duì)列,誰來向這個(gè)隊(duì)列里面?zhèn)魅朊钅兀?/strong>
很明顯是服務(wù)員,當(dāng)客戶點(diǎn)菜完成,服務(wù)員就會(huì)執(zhí)行菜單,現(xiàn)在執(zhí)行菜單就相當(dāng)于把菜單直接傳遞給后廚,也就是要把菜單里的所有命令對(duì)象加入到命令隊(duì)列里面。因此菜單對(duì)象的實(shí)現(xiàn)需要改變,示例代碼如下:
/**
* 菜單對(duì)象,是個(gè)宏命令對(duì)象
*/
public class MenuCommand implements Command {
/**
* 用來記錄組合本菜單的多道菜品,也就是多個(gè)命令對(duì)象
*/
private Collection<Command> col = new ArrayList<Command>();
/**
* 點(diǎn)菜,把菜品加入到菜單中
* @param cmd 客戶點(diǎn)的菜
*/
public void addCommand(Command cmd){
col.add(cmd);
}
public void setCookApi(CookApi cookApi){
//什么都不用做
}
public int getTableNum(){
//什么都不用做
return 0;
}
/**
* 獲取菜單中的多個(gè)命令對(duì)象
* @return 菜單中的多個(gè)命令對(duì)象
*/
public Collection<Command> getCommands(){
return this.col;
}
public void execute() {
//執(zhí)行菜單就是把菜單傳遞給后廚
CommandQueue.addMenu(this);
}
}
(6)現(xiàn)在有了命令隊(duì)列,也有人負(fù)責(zé)向隊(duì)列里面添加命令了,可是誰來執(zhí)行命令隊(duì)列里面的命令呢?
答案是:由廚師從命令隊(duì)列里面獲取命令,并真正處理命令,而且廚師在處理命令前會(huì)把自己設(shè)置到命令對(duì)象里面去當(dāng)接收者,表示這個(gè)菜由我來實(shí)際做。
廚師對(duì)象的實(shí)現(xiàn),大致有如下的改變:
為了更好的體現(xiàn)命令隊(duì)列的用法,再說實(shí)際情況也是多個(gè)廚師,這里用多線程來模擬多個(gè)廚師,他們自己從命令隊(duì)列里面獲取命令,然后處理命令,然后再獲取下一個(gè),如此反復(fù),因此廚師類要實(shí)現(xiàn)多線程接口。
還有一個(gè)改變,為了在多線程中輸出信息,讓我們知道是哪一個(gè)廚師在執(zhí)行命令,給廚師添加了一個(gè)姓名的屬性,通過構(gòu)造方法傳入。
另外一個(gè)改變是為了在多線程中看出效果,在廚師真正做菜的方法里面使用隨機(jī)數(shù)模擬了一個(gè)做菜的時(shí)間。
好了,介紹完了改變的地方,一起看看代碼吧,示例代碼如下:
/**
* 廚師對(duì)象,做熱菜的廚師
*/
public class HotCook implements CookApi,Runnable{
/**
* 廚師姓名
*/
private String name;
/**
* 構(gòu)造方法,傳入廚師姓名
* @param name 廚師姓名
*/
public HotCook(String name){
this.name = name;
}
public void cook(int tableNum,String name) {
//每次做菜的時(shí)間是不一定的,用個(gè)隨機(jī)數(shù)來模擬一下
int cookTime = (int)(20 * Math.random());
System.out.println(this.name+"廚師正在為"+tableNum+"號(hào)桌做:"+name);
try {
//讓線程休息這么長時(shí)間,表示正在做菜
Thread.sleep(cookTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+"廚師為"+tableNum+"號(hào)桌做好了:"+name+",共計(jì)耗時(shí)="+cookTime+"秒");
}
public void run() {
while(true){
//到命令隊(duì)列里面獲取命令對(duì)象
Command cmd = CommandQueue.getOneCommand();
if(cmd != null){
//說明取到命令對(duì)象了,這個(gè)命令對(duì)象還沒有設(shè)置接收者
//因?yàn)榍懊娑歼€不知道到底哪一個(gè)廚師來真正執(zhí)行這個(gè)命令
//現(xiàn)在知道了,就是當(dāng)前廚師實(shí)例,設(shè)置到命令對(duì)象里面
cmd.setCookApi(this);
//然后真正執(zhí)行這個(gè)命令
cmd.execute();
}
//休息1秒
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(7)該來看看服務(wù)員類了,由于現(xiàn)在考慮了后廚的管理,因此從實(shí)際來看,這次服務(wù)員也不知道到底命令的真正接收者是誰了,也就是說服務(wù)員也不知道某個(gè)菜到底最后由哪一位廚師完成,所以服務(wù)員類就簡單了。
組裝命令對(duì)象和接收者的功能后移到廚師類的線程里面了,當(dāng)某個(gè)廚師從命令隊(duì)列里面獲取一個(gè)命令對(duì)象的時(shí)候,這個(gè)廚師就是這個(gè)命令的真正接收者。
/**
* 服務(wù)員,負(fù)責(zé)組合菜單,還負(fù)責(zé)執(zhí)行調(diào)用
*/
public class Waiter {
/**
* 持有一個(gè)宏命令對(duì)象——菜單
*/
private MenuCommand menuCommand = new MenuCommand();
/**
* 客戶點(diǎn)菜
* @param cmd 客戶點(diǎn)的菜,每道菜是一個(gè)命令對(duì)象
*/
public void orderDish(Command cmd){
//添加到菜單中
menuCommand.addCommand(cmd);
}
/**
* 客戶點(diǎn)菜完畢,表示要執(zhí)行命令了,這里就是執(zhí)行菜單這個(gè)組合命令
*/
public void orderOver(){
this.menuCommand.execute();
}
}
(8)在見到曙光之前,還有一個(gè)問題要解決,就是誰來啟動(dòng)多線程的廚師呢?
為了實(shí)現(xiàn)后廚的管理,為此專門定義一個(gè)后廚管理的類,在這個(gè)類里面去啟動(dòng)多個(gè)廚師的線程
。而且這種啟動(dòng)在運(yùn)行期間應(yīng)該只有一次。示例代碼如下:
/**
* 后廚的管理類,通過此類讓后廚的廚師進(jìn)行運(yùn)行狀態(tài)
*/
public class CookManager {
/**
* 用來控制是否需要?jiǎng)?chuàng)建廚師,如果已經(jīng)創(chuàng)建過了就不要再執(zhí)行了
*/
private static boolean runFlag = false;
/**
* 運(yùn)行廚師管理,創(chuàng)建廚師對(duì)象并啟動(dòng)他們相應(yīng)的線程,
* 無論運(yùn)行多少次,創(chuàng)建廚師對(duì)象和啟動(dòng)線程的工作就只做一次
*/
public static void runCookManager(){
if(!runFlag){
runFlag = true;
//創(chuàng)建三位廚師
HotCook cook1 = new HotCook("張三");
HotCook cook2 = new HotCook("李四");
HotCook cook3 = new HotCook("王五");
//啟動(dòng)他們的線程
Thread t1 = new Thread(cook1);
t1.start();
Thread t2 = new Thread(cook2);
t2.start();
Thread t3 = new Thread(cook3);
t3.start();
}
}
}
(9)曙光來臨了,寫個(gè)客戶端測試測試,示例代碼如下:
public class Client {
public static void main(String[] args) {
//先要啟動(dòng)后臺(tái),讓整個(gè)程序運(yùn)行起來
CookManager.runCookManager();
//為了簡單,直接用循環(huán)模擬多個(gè)桌號(hào)點(diǎn)菜
for(int i = 0;i<5;i++){
//創(chuàng)建服務(wù)員
Waiter waiter = new Waiter();
//創(chuàng)建命令對(duì)象,就是要點(diǎn)的菜
Command chop = new ChopCommand(i);
Command duck = new DuckCommand(i);
//點(diǎn)菜,就是把這些菜讓服務(wù)員記錄下來
waiter.orderDish(chop);
waiter.orderDish(duck);
//點(diǎn)菜完畢
waiter.orderOver();
}
}
}
(10)運(yùn)行一下,看看效果,可能每次運(yùn)行的效果不一樣,畢竟是使用多線程在處理請(qǐng)求隊(duì)列,某次運(yùn)行的結(jié)果如下:
好好觀察上面的數(shù)據(jù),在多線程環(huán)境下,雖然保障了命令對(duì)象取出的順序是先進(jìn)先出,但是究竟是哪一個(gè)廚師來做,還有具體做多長時(shí)間都是不定的
。
3.7 命令模式的優(yōu)缺點(diǎn)##
- 更松散的耦合
命令模式使得發(fā)起命令的對(duì)象——客戶端,和具體實(shí)現(xiàn)命令的對(duì)象——接收者對(duì)象完全解耦,也就是說發(fā)起命令的對(duì)象,完全不知道具體實(shí)現(xiàn)對(duì)象是誰,也不知道如何實(shí)現(xiàn)。
- 更動(dòng)態(tài)的控制
命令模式把請(qǐng)求封裝起來,可以動(dòng)態(tài)對(duì)它進(jìn)行參數(shù)化、隊(duì)列化和日志化等操作,從而使得系統(tǒng)更靈活。
- 能很自然的復(fù)合命令
命令模式中的命令對(duì)象,能夠很容易的組合成為復(fù)合命令,就是前面講的宏命令,從而使系統(tǒng)操作更簡單,功能更強(qiáng)大。
- 更好的擴(kuò)展性
由于發(fā)起命令的對(duì)象和具體的實(shí)現(xiàn)完全解耦,因此擴(kuò)展新的命令就很容易,只需要實(shí)現(xiàn)新的命令對(duì)象,然后在裝配的時(shí)候,把具體的實(shí)現(xiàn)對(duì)象設(shè)置到命令對(duì)象里面,然后就可以使用這個(gè)命令對(duì)象,已有的實(shí)現(xiàn)完全不用變化。
3.8 思考命令模式##
- 命令模式的本質(zhì)
命令模式的本質(zhì):封裝請(qǐng)求。
前面講了,命令模式的關(guān)鍵就是把請(qǐng)求封裝成為命令對(duì)象,然后就可以對(duì)這個(gè)對(duì)象進(jìn)行一系列的處理了
,比如上面講到的參數(shù)化配置、可撤銷操作、宏命令、隊(duì)列請(qǐng)求、日志請(qǐng)求等功能處理。
- 何時(shí)選用命令模式
建議在如下情況中,選用命令模式:
如果需要抽象出需要執(zhí)行的動(dòng)作,并參數(shù)化這些對(duì)象,可以選用命令模式,把這些需要執(zhí)行的動(dòng)作抽象成為命令,然后實(shí)現(xiàn)命令的參數(shù)化配置
如果需要在不同的時(shí)刻指定、排列和執(zhí)行請(qǐng)求,可以選用命令模式,把這些請(qǐng)求封裝成為命令對(duì)象,然后實(shí)現(xiàn)把請(qǐng)求隊(duì)列化
如果需要支持取消操作,可以選用命令模式,通過管理命令對(duì)象,能很容易的實(shí)現(xiàn)命令的恢復(fù)和重做的功能
如果需要支持當(dāng)系統(tǒng)崩潰時(shí),能把對(duì)系統(tǒng)的操作功能重新執(zhí)行一遍,可以選用命令模式,把這些操作功能的請(qǐng)求封裝成命令對(duì)象,然后實(shí)現(xiàn)日志命令,就可以在系統(tǒng)恢復(fù)回來后,通過日志獲取命令列表,從而重新執(zhí)行一遍功能
在需要事務(wù)的系統(tǒng)中,可以選用命令模式,命令模式提供了對(duì)事務(wù)進(jìn)行建模的方法,命令模式有一個(gè)別名就是Transaction。
3.9 退化的命令模式##
在領(lǐng)會(huì)了命令模式本質(zhì)后,來思考一個(gè)命令模式退化的情況。
前面講到了智能命令,如果命令的實(shí)現(xiàn)對(duì)象超級(jí)智能,實(shí)現(xiàn)了命令所要求的功能,那么就不需要接收者了,既然沒有了接收者,那么也就不需要組裝者了
。
- 舉個(gè)最簡單的示例來說明
比如現(xiàn)在要實(shí)現(xiàn)一個(gè)打印服務(wù),由于非常簡單,所以基本上就沒有什么講述,依次來看,命令接口定義如下:
public interface Command {
public void execute();
}
命令的實(shí)現(xiàn)示例代碼如下:
public class PrintService implements Command{
/**
* 要輸出的內(nèi)容
*/
private String str = "";
/**
* 構(gòu)造方法,傳入要輸出的內(nèi)容
* @param s 要輸出的內(nèi)容
*/
public PrintService(String s){
str = s;
}
public void execute() {
//智能的體現(xiàn),自己知道怎么實(shí)現(xiàn)命令所要求的功能,并真的實(shí)現(xiàn)了相應(yīng)的功能,不再轉(zhuǎn)調(diào)接收者了
System.out.println("打印的內(nèi)容為="+str);
}
}
此時(shí)的Invoker示例代碼如下:
public class Invoker {
/**
* 持有命令對(duì)象
*/
private Command cmd = null;
/**
* 設(shè)置命令對(duì)象
* @param cmd 命令對(duì)象
*/
public void setCmd(Command cmd){
this.cmd = cmd;
}
/**
* 開始打印
*/
public void startPrint(){
//執(zhí)行命令的功能
this.cmd.execute();
}
}
最后看看客戶端的代碼,示例如下:
public class Client {
public static void main(String[] args) {
//準(zhǔn)備要發(fā)出的命令
Command cmd = new PrintService("退化的命令模式示例");
//設(shè)置命令給持有者
Invoker invoker = new Invoker();
invoker.setCmd(cmd);
//按下按鈕,真正啟動(dòng)執(zhí)行命令
invoker.startPrint();
}
}
測試結(jié)果如下:
打印的內(nèi)容為=退化的命令模式示例
- 繼續(xù)變化
如果此時(shí)繼續(xù)變化,Invoker也開始變得智能化,在Invoker的startPrint方法里面,Invoker加入了一些實(shí)現(xiàn),同時(shí)Invoker對(duì)持有命令也有意見,覺得自己是個(gè)傀儡,要求改變一下,直接在調(diào)用方法的時(shí)候傳遞命令對(duì)象進(jìn)來
,示例代碼如下:
public class Invoker {
public void startPrint(Command cmd){
System.out.println("在Invoker中,輸出服務(wù)前");
cmd.execute();
System.out.println("輸出服務(wù)結(jié)束");
}
}
看起來Invoker退化成一個(gè)方法了。
這個(gè)時(shí)候Invoker很高興,宣稱自己是一個(gè)智能的服務(wù),不再是一個(gè)傻傻的轉(zhuǎn)調(diào)者,而是有自己功能的服務(wù)了。這個(gè)時(shí)候Invoker調(diào)用命令對(duì)象的執(zhí)行方法,也不叫轉(zhuǎn)調(diào),改名叫“回調(diào)”,意思是在我Invoker需要的時(shí)候,會(huì)回調(diào)你命令對(duì)象,命令對(duì)象你就乖乖的寫好實(shí)現(xiàn),等我“回調(diào)”你就可以了
。
事實(shí)上這個(gè)時(shí)候的命令模式的實(shí)現(xiàn),基本上就等同于Java回調(diào)機(jī)制的實(shí)現(xiàn),可能有些朋友看起來感覺還不是佷像,那是因?yàn)樵贘ava回調(diào)機(jī)制的常見實(shí)現(xiàn)上,經(jīng)常沒有單獨(dú)的接口實(shí)現(xiàn)類,而是采用匿名內(nèi)部類的方式來實(shí)現(xiàn)的
。
- 再進(jìn)一步
把單獨(dú)實(shí)現(xiàn)命令接口的類改成用匿名內(nèi)部類實(shí)現(xiàn),這個(gè)時(shí)候就只剩下命令的接口、Invoker類,還有客戶端了
。
為了使用匿名內(nèi)部類,還要設(shè)置要輸出的值,對(duì)命令接口做點(diǎn)小改動(dòng),增加一個(gè)設(shè)置輸出值的方法,示例代碼如下:
public interface Command {
public void execute();
/**
* 設(shè)置要輸出的內(nèi)容
* @param s 要輸出的內(nèi)容
*/
public void setStr(String s);
}
此時(shí)Invoker就是上面那個(gè),而客戶端會(huì)有些改變,客戶端的示例代碼如下:
public class Client {
public static void main(String[] args) {
//準(zhǔn)備要發(fā)出的命令,沒有具體實(shí)現(xiàn)類了
//匿名內(nèi)部類來實(shí)現(xiàn)命令
Command cmd = new Command() {
private String str = "";
public void setStr(String s) {
str = s;
}
public void execute() {
System.out.println("打印的內(nèi)容為="+str);
}
};
cmd.setStr("退化的命令模式類似于Java回調(diào)的示例");
//這個(gè)時(shí)候的Invoker或許該稱為服務(wù)了
Invoker invoker = new Invoker();
//按下按鈕,真正啟動(dòng)執(zhí)行命令
invoker.startPrint(cmd);
}
}
運(yùn)行測試一下,結(jié)果如下:
在Invoker中,輸出服務(wù)前
打印的內(nèi)容為=退化的命令模式類似于Java回調(diào)的示例
輸出服務(wù)結(jié)束
- 現(xiàn)在是不是看出來了,
這個(gè)時(shí)候的命令模式的實(shí)現(xiàn),基本上就等同于Java回調(diào)機(jī)制的實(shí)現(xiàn)
。這也是很多人大談特談命令模式可以實(shí)現(xiàn)Java回調(diào)的意思。
當(dāng)然更狠的是連Invoker也不要了,直接把那個(gè)方法搬到Client中,那樣測試起來就更方便了。在實(shí)際開發(fā)中,應(yīng)用命令模式來實(shí)現(xiàn)回調(diào)機(jī)制的時(shí)候,Invoker通常還是有的,但可以智能化實(shí)現(xiàn),更準(zhǔn)確的說Invoker充當(dāng)客戶調(diào)用的服務(wù)實(shí)現(xiàn),而回調(diào)的方法只是實(shí)現(xiàn)服務(wù)功能中的一個(gè)或者幾個(gè)步驟。
3.10 相關(guān)模式##
- 命令模式和組合模式
這兩個(gè)模式可以組合使用。
在命令模式中,實(shí)現(xiàn)宏命令的功能,就可以使用組合模式來實(shí)現(xiàn)
。前面的示例并沒有按照組合模式來做,那是為了保持示例的簡單,還有突出命令模式的實(shí)現(xiàn),這點(diǎn)請(qǐng)注意。
- 命令模式和備忘錄模式
這兩個(gè)模式可以組合使用。
在命令模式中,實(shí)現(xiàn)可撤銷操作功能時(shí),前面講了有兩種實(shí)現(xiàn)方式,其中有一種就是保存命令執(zhí)行前的狀態(tài),撤銷的時(shí)候就把狀態(tài)恢復(fù)回去
。如果采用這種方式實(shí)現(xiàn),就可以考慮使用備忘錄模式。
如果狀態(tài)存儲(chǔ)在命令對(duì)象里面,那么還可以使用原型模式,把命令對(duì)象當(dāng)作原型來克隆一個(gè)新的對(duì)象,然后把克隆出來的對(duì)象通過備忘錄模式存放。
- 命令模式和模板方法模式
這兩個(gè)模式從某種意義上有相似的功能,命令模式可以作為模板方法的一種替代模式,也就是說命令模式可以模仿實(shí)現(xiàn)模板方法模式的功能
。
如同前面講述的退化的命令模式可以實(shí)現(xiàn)Java的回調(diào),而Invoker智能化后向服務(wù)進(jìn)化,如果Invoker的方法就是一個(gè)算法骨架,其中有兩步在這個(gè)骨架里面沒有具體實(shí)現(xiàn),需要外部來實(shí)現(xiàn),這個(gè)時(shí)候就可以通過回調(diào)命令接口來實(shí)現(xiàn)。
而類似的功能在模板方法里面,一個(gè)算法骨架,其中有兩步在這個(gè)骨架里面沒有具體實(shí)現(xiàn),是先調(diào)用抽象方法,然后等待子類來實(shí)現(xiàn)。
可以看出雖然實(shí)現(xiàn)方式不一樣,但是可以實(shí)現(xiàn)相同的功能。