博客原文地址 折騰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圖
從上方的時(shí)序圖中可以看出運(yùn)行的順序,Invoker執(zhí)行execute方法督勺,調(diào)用Command1對(duì)象渠羞,Command1執(zhí)行action1方法調(diào)用Receiver1對(duì)象。
干貨代碼
普通的命令模式
現(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();
}
}
請(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ǔ)句谷朝。
抽象命令
@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();
}
}
抽象命令增加了撤銷操作
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