【行為型模式十七】命令模式-2(Command)

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è),再處理,僅此而已,廚師不知道也不管客戶是誰。

  1. 如何實(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é)果如下:

Paste_Image.png

好好觀察上面的數(shù)據(jù),在多線程環(huán)境下,雖然保障了命令對(duì)象取出的順序是先進(jìn)先出,但是究竟是哪一個(gè)廚師來做,還有具體做多長時(shí)間都是不定的

3.7 命令模式的優(yōu)缺點(diǎn)##

  1. 更松散的耦合

命令模式使得發(fā)起命令的對(duì)象——客戶端,和具體實(shí)現(xiàn)命令的對(duì)象——接收者對(duì)象完全解耦,也就是說發(fā)起命令的對(duì)象,完全不知道具體實(shí)現(xiàn)對(duì)象是誰,也不知道如何實(shí)現(xiàn)。

  1. 更動(dòng)態(tài)的控制

命令模式把請(qǐng)求封裝起來,可以動(dòng)態(tài)對(duì)它進(jìn)行參數(shù)化、隊(duì)列化和日志化等操作,從而使得系統(tǒng)更靈活。

  1. 能很自然的復(fù)合命令

命令模式中的命令對(duì)象,能夠很容易的組合成為復(fù)合命令,就是前面講的宏命令,從而使系統(tǒng)操作更簡單,功能更強(qiáng)大。

  1. 更好的擴(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 思考命令模式##

  1. 命令模式的本質(zhì)

命令模式的本質(zhì):封裝請(qǐng)求。

前面講了,命令模式的關(guān)鍵就是把請(qǐng)求封裝成為命令對(duì)象,然后就可以對(duì)這個(gè)對(duì)象進(jìn)行一系列的處理了,比如上面講到的參數(shù)化配置、可撤銷操作、宏命令、隊(duì)列請(qǐng)求、日志請(qǐng)求等功能處理。

  1. 何時(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)了命令所要求的功能,那么就不需要接收者了,既然沒有了接收者,那么也就不需要組裝者了

  1. 舉個(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)容為=退化的命令模式示例
  1. 繼續(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)的

  1. 再進(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é)束  
  1. 現(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)相同的功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • 【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★★☆】直接出處:命令模式梳理和學(xué)習(xí):https://github.com/...
    BruceOuyang閱讀 842評(píng)論 0 3
  • 1.初識(shí)命令模式 將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及...
    王偵閱讀 721評(píng)論 0 2
  • 1 意圖 將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤...
    10xjzheng閱讀 605評(píng)論 0 1
  • javascript設(shè)計(jì)模式與開發(fā)實(shí)踐 設(shè)計(jì)模式 每個(gè)設(shè)計(jì)模式我們需要從三點(diǎn)問題入手: 定義 作用 用法與實(shí)現(xiàn) 單...
    穿牛仔褲的蚊子閱讀 4,081評(píng)論 0 13
  • 命令模式(行為型) 一、相關(guān)概述 1). 案例引入 Sunny軟件公司開發(fā)人員為公司內(nèi)部OA系統(tǒng)開發(fā)了一個(gè)桌面版應(yīng)...
    哈哈大圣閱讀 288評(píng)論 0 0