折騰Java設(shè)計(jì)模式之命令模式

博客原文地址 折騰Java設(shè)計(jì)模式之命令模式

命令模式

wiki上的描述 Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also allows for the support of undoable operations.

翻譯意思,把請(qǐng)求封裝成一個(gè)對(duì)象陷虎,從而允許我們可以對(duì)客戶端的不同請(qǐng)求進(jìn)行參數(shù)化到踏,以及對(duì)請(qǐng)求進(jìn)行排隊(duì)或記錄。還允許支持撤銷操作尚猿∥迅澹看起來(lái)好像很復(fù)雜,很難理解凿掂。

通俗簡(jiǎn)單理解伴榔,它就是將請(qǐng)求封裝成一個(gè)對(duì)象纹蝴,在這里就是這個(gè)對(duì)象就是命令,而這個(gè)命令就是將請(qǐng)求方和執(zhí)行方分離隔開(kāi)踪少。從而每一個(gè)命令其實(shí)就是操作,而這樣的流程就是請(qǐng)求方發(fā)出請(qǐng)求要求執(zhí)行某操作,接收方收到請(qǐng)求后并執(zhí)行對(duì)應(yīng)的操作厉斟。這樣下來(lái)珍语,請(qǐng)求方和接收方就解耦了,使得請(qǐng)求方完全不知道接受的操作方法集漾,從也不會(huì)知道接收方是何時(shí)接受到請(qǐng)求的切黔,又是何時(shí)執(zhí)行操作的,又是怎么執(zhí)行操作的具篇。

具體的角色

Command(抽象命令類):抽象命令類一般是一個(gè)抽象類或接口纬霞,在其中聲明了用于執(zhí)行請(qǐng)求的execute()等方法,通過(guò)這些方法可以調(diào)用請(qǐng)求接收者的相關(guān)操作栽连。

ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類险领,實(shí)現(xiàn)了在抽象命令類中聲明的方法,它對(duì)應(yīng)具體的接收者對(duì)象秒紧,將接收者對(duì)象的動(dòng)作綁定其中绢陌。在實(shí)現(xiàn)execute()方法時(shí),將調(diào)用接收者對(duì)象的相關(guān)操作(Action)熔恢。

Invoker(請(qǐng)求方):調(diào)用者即請(qǐng)求發(fā)送者脐湾,它通過(guò)命令對(duì)象來(lái)執(zhí)行請(qǐng)求。一個(gè)調(diào)用者并不需要在設(shè)計(jì)時(shí)確定其接收者叙淌,因此它只與抽象命令類之間存在關(guān)聯(lián)關(guān)系秤掌。在程序運(yùn)行時(shí)可以將一個(gè)具體命令對(duì)象注入其中,再調(diào)用具體命令對(duì)象的execute()方法鹰霍,從而實(shí)現(xiàn)間接調(diào)用請(qǐng)求接收者的相關(guān)操作闻鉴。

Receiver(接收方):接收者執(zhí)行與請(qǐng)求相關(guān)的操作,它具體實(shí)現(xiàn)對(duì)請(qǐng)求的業(yè)務(wù)處理茂洒。

Client(客戶端):創(chuàng)建具體命令的對(duì)象并且設(shè)置命令對(duì)象的接受者孟岛。

再來(lái)看看UML圖

img
img

從上方的時(shí)序圖中可以看出運(yùn)行的順序,Invoker執(zhí)行execute方法督勺,調(diào)用Command1對(duì)象渠羞,Command1執(zhí)行action1方法調(diào)用Receiver1對(duì)象。

干貨代碼

源碼在我的GitHub地址

普通的命令模式

現(xiàn)在結(jié)合下上回說(shuō)到的狀態(tài)模式一起來(lái)實(shí)現(xiàn)這個(gè)風(fēng)扇的左轉(zhuǎn)和右轉(zhuǎn)功能智哀,這次把他用命令模式來(lái)代替之前風(fēng)扇的轉(zhuǎn)動(dòng)次询,把它當(dāng)做命令來(lái)。

客戶端簡(jiǎn)單的定義請(qǐng)求方和接收方以及對(duì)于的左轉(zhuǎn)命令和右轉(zhuǎn)命令瓷叫,設(shè)置命令后對(duì)應(yīng)的執(zhí)行命令屯吊。

public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Receiver receiver = new Receiver();
        Command leftCommand = new LeftCommand(receiver);
        Command rightCommand = new RightCommand(receiver);

        invoker.setCommand(rightCommand);
        invoker.execute();
        invoker.execute();
        invoker.execute();

        invoker.setCommand(leftCommand);
        invoker.execute();
        invoker.execute();
    }
}
image-20190102115207650

請(qǐng)求方

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private Command command;

    public void execute() {
        command.execute();
    }

}

抽象命令

public interface Command {

    void execute();
}

開(kāi)關(guān)左轉(zhuǎn)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeftCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.left();
    }
}

開(kāi)關(guān)右轉(zhuǎn)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RightCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.right();
    }
}

接收方

public class Receiver {

    private Context context = new Context(new CloseLevelState());

    public void left() {
        context.left();
    }

    public void right() {
        context.right();
    }
}

通過(guò)命令模式把左轉(zhuǎn)和右轉(zhuǎn)封裝成命令送巡,以及之前的狀態(tài)模式變更風(fēng)扇的狀態(tài)。本次就是通過(guò)狀態(tài)模式和命令模式實(shí)現(xiàn)了一個(gè)風(fēng)扇開(kāi)關(guān)左右轉(zhuǎn)的功能雌芽。

宏命令或者叫做組合命令

設(shè)計(jì)一組命令授艰,簡(jiǎn)單的處理事情,打印一句話世落,封裝成一組命令淮腾。這次我們用了Java8來(lái)寫,可以使用lambda屉佳。

@Slf4j
public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        log.info("初始化ABC3個(gè)命令”);
        Command aCommand = () -> log.info("A處理這個(gè)請(qǐng)求”);
        invoker.addCommand(aCommand);
        invoker.addCommand(() -> log.info("B處理這個(gè)請(qǐng)求”));
        invoker.addCommand(() -> log.info("C處理這個(gè)請(qǐng)求”));
        invoker.execute();

        log.info("—————————————“);
        log.info("加入新命令D”);
        invoker.addCommand(() -> log.info("D處理這個(gè)請(qǐng)求”));
        invoker.execute();

        log.info("—————————————“);
        log.info("加入新命令E”);
        invoker.addCommand(() -> log.info("E處理這個(gè)請(qǐng)求”));
        invoker.execute();

        log.info("—————————————“);
        log.info("移除命令A(yù)”);
        invoker.removeCommand(aCommand);
        invoker.execute();
    }
}

打印語(yǔ)句谷朝。

image-20190102143032334

抽象命令

@FunctionalInterface
public interface Command {

    void execute();
}

請(qǐng)求方

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private List<Command> commandList = Lists.newArrayList();

    public void addCommand(Command command) {
        commandList.add(command);
    }

    public void removeCommand(Command command) {
        commandList.remove(command);
    }

    public void execute() {
        if(CollectionUtils.isEmpty(commandList)) {
            return;
        }
        commandList.stream().forEach(command -> command.execute());
    }

}

撤銷操作

在普通的命令模式的基礎(chǔ)上,增加了撤銷操作武花,在這里的撤銷操作圆凰,其實(shí)即為左轉(zhuǎn)時(shí)的右轉(zhuǎn),右轉(zhuǎn)時(shí)的左轉(zhuǎn)体箕。

@Slf4j
public class Client {

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Receiver receiver = new Receiver();
        Command leftCommand = new LeftCommand(receiver);
        Command rightCommand = new RightCommand(receiver);

        invoker.setCommand(rightCommand);
        invoker.execute();
        invoker.execute();
        invoker.execute();
        invoker.undo();
        invoker.undo();

        invoker.setCommand(leftCommand);
        invoker.execute();
        invoker.undo();
    }
}
image-20190102145507099

抽象命令增加了撤銷操作

public interface Command {

    void execute();

    void undo();
}

具體左轉(zhuǎn)時(shí)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeftCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.left();
    }

    @Override
    public void undo() {
        receiver.right();
    }
}

右轉(zhuǎn)時(shí)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RightCommand implements Command {

    private Receiver receiver;

    @Override
    public void execute() {
        receiver.right();
    }

    @Override
    public void undo() {
        receiver.left();
    }
}

請(qǐng)求方

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoker {

    private Command command;

    public void execute() {
        command.execute();
    }

    public void undo() {
        command.undo();
    }

}

接收方

public class Receiver {

    private Context context = new Context(new CloseLevelState());

    public void left() {
        context.left();
    }

    public void right() {
        context.right();
    }
}

命令模式總結(jié)

優(yōu)點(diǎn)

(1) 降低系統(tǒng)的耦合度专钉。由于請(qǐng)求者與接收者之間不存在直接引用,因此請(qǐng)求者與接收者之間實(shí)現(xiàn)完全解耦累铅,相同的請(qǐng)求者可以對(duì)應(yīng)不同的接收者跃须,同樣,相同的接收者也可以供不同的請(qǐng)求者使用娃兽,兩者之間具有良好的獨(dú)立性菇民。

(2) 新的命令可以很容易地加入到系統(tǒng)中。由于增加新的具體命令類不會(huì)影響到其他類投储,因此增加新的具體命令類很容易第练,無(wú)須修改原有系統(tǒng)源代碼,甚至客戶類代碼玛荞,滿足“開(kāi)閉原則”的要求娇掏。

(3) 可以比較容易地設(shè)計(jì)一個(gè)命令隊(duì)列或宏命令(組合命令)

(4) 為請(qǐng)求的撤銷(Undo)和恢復(fù)(Redo)操作提供了一種設(shè)計(jì)和實(shí)現(xiàn)方案勋眯。

缺點(diǎn)

使用命令模式可能會(huì)導(dǎo)致某些系統(tǒng)有過(guò)多的具體命令類驹碍。因?yàn)獒槍?duì)每一個(gè)對(duì)請(qǐng)求接收者的調(diào)用操作都需要設(shè)計(jì)一個(gè)具體命令類,因此在某些系統(tǒng)中可能需要提供大量的具體命令類凡恍,這將影響命令模式的使用。

適用場(chǎng)景

(1) 系統(tǒng)需要將請(qǐng)求調(diào)用者和請(qǐng)求接收者解耦怔球,使得調(diào)用者和接收者不直接交互嚼酝。請(qǐng)求調(diào)用者無(wú)須知道接收者的存在,也無(wú)須知道接收者是誰(shuí)竟坛,接收者也無(wú)須關(guān)心何時(shí)被調(diào)用闽巩。

(2) 系統(tǒng)需要在不同的時(shí)間指定請(qǐng)求钧舌、將請(qǐng)求排隊(duì)和執(zhí)行請(qǐng)求。一個(gè)命令對(duì)象和請(qǐng)求的初始調(diào)用者可以有不同的生命期涎跨,換言之洼冻,最初的請(qǐng)求發(fā)出者可能已經(jīng)不在了,而命令對(duì)象本身仍然是活動(dòng)的隅很,可以通過(guò)該命令對(duì)象去調(diào)用請(qǐng)求接收者撞牢,而無(wú)須關(guān)心請(qǐng)求調(diào)用者的存在性,可以通過(guò)請(qǐng)求日志文件等機(jī)制來(lái)具體實(shí)現(xiàn)叔营。

(3) 系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作屋彪。

(4) 系統(tǒng)需要將一組操作組合在一起形成宏命令。

(5)線程池有一個(gè)addTash方法绒尊,將任務(wù)添加到待完成的隊(duì)列中畜挥,隊(duì)列中的元素就是命令對(duì)象,通常的就是一個(gè)公共接口婴谱,像我們常用的java.lang.Runnable接口蟹但。

(6)java8之后,最好在Command接口中@FunctionalInterface修飾谭羔,這樣具體的命令就可以使用lambda表達(dá)式啦华糖。

Java中的使用

將操作封裝到對(duì)象內(nèi),以便存儲(chǔ)口糕,傳遞和返回缅阳。

java.lang.Runnable

javax.swing.Action

文章參考

java設(shè)計(jì)模式之命令模式

細(xì)數(shù)JDK里的設(shè)計(jì)模式

wiki的命令模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市景描,隨后出現(xiàn)的幾起案子十办,更是在濱河造成了極大的恐慌,老刑警劉巖超棺,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件向族,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棠绘,警方通過(guò)查閱死者的電腦和手機(jī)件相,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氧苍,“玉大人夜矗,你說(shuō)我怎么就攤上這事∪门埃” “怎么了紊撕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赡突。 經(jīng)常有香客問(wèn)我对扶,道長(zhǎng)区赵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任浪南,我火速辦了婚禮笼才,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘络凿。我一直安慰自己骡送,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布喷众。 她就那樣靜靜地躺著各谚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪到千。 梳的紋絲不亂的頭發(fā)上昌渤,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音憔四,去河邊找鬼膀息。 笑死,一個(gè)胖子當(dāng)著我的面吹牛了赵,可吹牛的內(nèi)容都是我干的潜支。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柿汛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼冗酿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起络断,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裁替,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后貌笨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體弱判,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年锥惋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昌腰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膀跌,死狀恐怖遭商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捅伤,我是刑警寧澤株婴,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響困介,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘸际,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一座哩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粮彤,春花似錦根穷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惫周,卻和暖如春尘惧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背递递。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工喷橙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人登舞。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓贰逾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親菠秒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疙剑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353