Java設(shè)計(jì)模式百例 - 命令模式

本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/command

命令模式(Command Pattern)是一種數(shù)據(jù)驅(qū)動(dòng)的設(shè)計(jì)模式,它屬于行為型模式。請(qǐng)求以命令的形式包裹在對(duì)象中,并傳給調(diào)用對(duì)象。調(diào)用對(duì)象尋找可以處理該命令的合適的處理對(duì)象,并把該命令傳給這個(gè)處理對(duì)象,該處理對(duì)象執(zhí)行命令。

命令模式把發(fā)出命令的責(zé)任和執(zhí)行命令的責(zé)任分開,委派個(gè)不同的對(duì)象。什么情況下需要吧發(fā)出命令的責(zé)任和執(zhí)行命令的責(zé)任分開呢?不好意思,按照我的尿性,又要搬出畫圖的例子了。

例子

我們先考慮發(fā)出命令和執(zhí)行命令耦合起來的情況,看有啥不好。比如畫圖有如下幾個(gè)命令:畫形狀、填充顏色、組合形狀等,每個(gè)操作命令的執(zhí)行由不同的對(duì)象來實(shí)現(xiàn)。當(dāng)用戶在執(zhí)行不同的操作的時(shí)候,畫圖軟件有個(gè)統(tǒng)一的邏輯來處理:

// 以下邏輯位于“命令發(fā)出者”的方法內(nèi):
if(要畫形狀){
    形狀處理對(duì)象.畫形狀("圓形");
}
if(要填充顏色){
    顏色處理對(duì)象.填充顏色("紅色");
}
if(要組合形狀){
    組合處理對(duì)象.組合形狀([]{"紅色圓形","藍(lán)色矩形","黃色三角形");
}

這里有幾個(gè)不太靈活的地方:

  1. “命令發(fā)出者”要同所有的“命令執(zhí)行者”維持關(guān)系(前者要有后者的所有引用),只要增加一種命令執(zhí)行者,就需要修改命令發(fā)出者的代碼邏輯,顯然不符合“開閉”原則的吧;尤其是命令發(fā)出者如果是不開放修改的(比如位于框架中),就很難保證擴(kuò)展性了;
  2. 命令執(zhí)行有可能失敗,或者Ctrl+Z回退,這種操作顯然是經(jīng)常遇到的,對(duì)于”命令發(fā)出者“的邏輯復(fù)雜度來說,顯然又是雪上加霜。
before-decoupling.png

我們?cè)賮砜匆幌?,?yīng)用命令模式如何解耦以及解耦后有什么好處。

將發(fā)出命令和執(zhí)行命令解耦,即“發(fā)出命令者”不直接調(diào)用“執(zhí)行命令者”的方法,而是通過一個(gè)“中間媒介”,這個(gè)“中間媒介”就是“命令”。從而由原來的“發(fā)出命令者 - 執(zhí)行命令者”的關(guān)系變成“發(fā)出命令者 - 命令 - 執(zhí)行命令者”。

“發(fā)出命令者”只關(guān)心命令的執(zhí)行,并不關(guān)心由誰(shuí)來執(zhí)行。從而它手中不再握有一系列的“執(zhí)行命令者”的引用,而是只有處理命令(或命令集合,如命令隊(duì)列)的引用。而命令本身有“執(zhí)行命令者”的引用,從而知道找誰(shuí)去完成命令的具體執(zhí)行。

after-decoupling.png

當(dāng)然,要做到完全解耦和保證靈活性,必須針對(duì)“命令”進(jìn)行面向接口的設(shè)計(jì),也就是“發(fā)出命令者”手里的只有抽象類型的命令對(duì)象,才能做到“不care”。這都是設(shè)計(jì)模式的老掉牙的套路了,不再多說。

如此,剛才提到的兩個(gè)不太靈活的問題也就迎刃而解了??创a嘍~

發(fā)出命令者 DrawingApp.java

public class DrawingApp {
    private List<Command> commands = new ArrayList<Command>();
    public void takeCommand(Command command) {
        commands.add(command);
    }
    public void CommandsDone() {
        for (Command command:commands) {
            command.doCmd();
        }
    }
    public void undoLastCommand() {
        commands.get(commands.size() - 1).undoCmd();
    }
}

看到它引用了命令的列表。

命令抽象 Command.java

public interface Command {
    void doCmd();
    void undoCmd();
}

具體命令 ShapeDrawing.java

public class ShapeDrawing implements Command {
    private ShapeDrawer drawer;
    private String arg;

    public ShapeDrawing(ShapeDrawer drawer, String arg) {
        this.drawer = drawer;
        this.arg = arg;
    }

    public void doCmd() {
        drawer.drawShape(arg);
    }

    public void undoCmd() {
        drawer.undrawShape();
    }
}

具體命令 ColorFilling.java

public class ColorFilling implements Command {
    private ColorFiller filler;
    private String arg;

    public ColorFilling(ColorFiller filler, String arg) {
        this.filler = filler;
        this.arg = arg;
    }

    public void doCmd() {
        filler.fillColor(arg);
    }

    public void undoCmd() {
        filler.unfillColor();
    }
}

兩個(gè)具體命令都引用了相應(yīng)的命令執(zhí)行者。

執(zhí)行命令者 ShapeDrawer.java

public class ShapeDrawer {
    public void drawShape(String shape) {
        System.out.println("畫了一個(gè)" + shape);
    }
    public void undrawShape() {
        System.out.println("撤銷剛才畫的形狀");
    }
}

執(zhí)行命令者 ColorFiller.java

public class ColorFiller {
    public void fillColor(String color) {
        System.out.println("填充" + color);
    }
    public void unfillColor() {
        System.out.println("撤銷填充的顏色");
    }
}

測(cè)試一下,發(fā)出三個(gè)命令,并撤銷最后一個(gè):

Client.java

public class Client {
    public static void main(String[] args) {
        DrawingApp app = new DrawingApp();
        ShapeDrawer shapeDrawer = new ShapeDrawer();
        ColorFiller colorFiller = new ColorFiller();
        Command drawCircle = new ShapeDrawing(shapeDrawer, "圓形");
        Command fillRed = new ColorFilling(colorFiller, "紅色");
        Command drawRectancle = new ShapeDrawing(shapeDrawer, "矩形");
        app.takeCommand(drawCircle);
        app.takeCommand(fillRed);
        app.takeCommand(drawRectancle);
        app.CommandsDone();
        app.undoLastCommand();
    }
}

輸出如下:

畫了一個(gè)圓形
填充紅色
畫了一個(gè)矩形
撤銷剛才畫的形狀

總結(jié)

通過以上例子,我們可以總結(jié)出命令模式的特點(diǎn):

  1. 將命令封裝為對(duì)象作為傳遞媒介;
  2. 命令發(fā)出者(invoker)和命令執(zhí)行者(receiver)是解耦的,通過命令對(duì)象“牽線”,invoker維護(hù)有命令(或命令集合)的引用,命令中有receiver的引用。

命令模式的應(yīng)用場(chǎng)景(轉(zhuǎn)載):

  1. Multi-level undo(多級(jí)undo操作)。如果系統(tǒng)需要實(shí)現(xiàn)多級(jí)回退操作,這時(shí)如果所有用戶的操作都以command對(duì)象的形式實(shí)現(xiàn),系統(tǒng)可以簡(jiǎn)單地用stack來保存最近執(zhí)行的命令,如果用戶需要執(zhí)行undo操作,系統(tǒng)只需簡(jiǎn)單地popup一個(gè)最近的command對(duì)象然后執(zhí)行它的undo()方法既可。
  2. Transactional behavior(原子事務(wù)行為)。借助command模式,可以簡(jiǎn)單地實(shí)現(xiàn)一個(gè)具有原子事務(wù)的行為。當(dāng)一個(gè)事務(wù)失敗時(shí),往往需要回退到執(zhí)行前的狀態(tài),可以借助command對(duì)象保存這種狀態(tài),簡(jiǎn)單地處理回退操作。
  3. Progress bars(狀態(tài)條)。假如系統(tǒng)需要按順序執(zhí)行一系列的命令操作,如果每個(gè)command對(duì)象都提供一個(gè)getEstimatedDuration()方法,那么系統(tǒng)可以簡(jiǎn)單地評(píng)估執(zhí)行狀態(tài)并顯示出合適的狀態(tài)條。
  4. Wizards(導(dǎo)航)。通常一個(gè)使用多個(gè)wizard頁(yè)面來共同完成一個(gè)簡(jiǎn)單動(dòng)作。一個(gè)自然的方法是使用一個(gè)command對(duì)象來封裝wizard過程,該command對(duì)象在第一個(gè)wizard頁(yè)面顯示時(shí)被創(chuàng)建,每個(gè)wizard頁(yè)面接收用戶輸入并設(shè)置到該command對(duì)象中,當(dāng)最后一個(gè)wizard頁(yè)面用戶按下“Finish”按鈕時(shí),可以簡(jiǎn)單地觸發(fā)一個(gè)事件調(diào)用execute()方法執(zhí)行整個(gè)動(dòng)作。通過這種方法,command類不包含任何跟用戶界面有關(guān)的代碼,可以分離用戶界面與具體的處理邏輯。
  5. GUI buttons and menu items(GUI按鈕與菜單條等等)。Swing系統(tǒng)里,用戶可以通過工具條按鈕,菜單按鈕執(zhí)行命令,可以用command對(duì)象來封裝命令的執(zhí)行。
  6. Thread pools(線程池)。通常一個(gè)典型的線程池實(shí)現(xiàn)類可能有一個(gè)名為addTask()的public方法,用來添加一項(xiàng)工作任務(wù)到任務(wù)隊(duì)列中。該任務(wù)隊(duì)列中的所有任務(wù)可以用command對(duì)象來封裝,通常這些command對(duì)象會(huì)實(shí)現(xiàn)一個(gè)通用的接口比如java.lang.Runnable。
  7. Macro recording(宏紀(jì)錄)。可以用command對(duì)象來封裝用戶的一個(gè)操作,這樣系統(tǒng)可以簡(jiǎn)單通過隊(duì)列保存一系列的command對(duì)象的狀態(tài)就可以記錄用戶的連續(xù)操作。這樣通過執(zhí)行隊(duì)列中的command對(duì)象,就可以完成"Play back"操作了。
  8. Networking。通過網(wǎng)絡(luò)發(fā)送command命令到其他機(jī)器上運(yùn)行。
  9. Parallel Processing(并發(fā)處理)。當(dāng)一個(gè)調(diào)用共享某個(gè)資源并被多個(gè)線程并發(fā)處理時(shí)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,697評(píng)論 18 399
  • 1 場(chǎng)景問題# 1.1 如何開機(jī)## 估計(jì)有些朋友看到這個(gè)標(biāo)題會(huì)非常奇怪,電腦裝配好了,如何開機(jī)?不就是按下啟動(dòng)按...
    七寸知架構(gòu)閱讀 2,889評(píng)論 1 59
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,563評(píng)論 19 139
  • 版本說明 os:CentOS 6.8 php:5.5.38 nginx:1.10.3 mysql:5.6 安裝步驟...
    wangtingkui閱讀 385評(píng)論 0 2
  • 發(fā)現(xiàn)美的眼睛,用鏡頭留住美的瞬間! 說說我愛上手機(jī)微距攝影的八個(gè)理由。 一、攝影讓你關(guān)注細(xì)節(jié) 當(dāng)你真正喜歡上攝影,...
    小盒子碎碎念閱讀 2,387評(píng)論 9 15

友情鏈接更多精彩內(nèi)容