目錄
本文的結(jié)構(gòu)如下:
- 什么是命令模式
- 為什么要用該模式
- 模式的結(jié)構(gòu)
- 代碼示例
- 優(yōu)點和缺點
- 適用環(huán)境
- 模式應(yīng)用
- 總結(jié)
一、前言
在軟件設(shè)計中,經(jīng)常需要向某些對象發(fā)送請求圃酵,但是并不知道請求的接收者是誰僧鲁,也不知道被請求的操作是哪個,我們只需在程序運行時指定具體的請求接收者即可绊困,此時,可以使用命令模式來進行設(shè)計响迂,使得請求發(fā)送者與請求接收者消除彼此之間的耦合考抄,讓對象之間的調(diào)用關(guān)系更加靈活。
二蔗彤、什么是命令模式
上面說了川梅,命令模式可以將請求發(fā)送者和接收者完全解耦,發(fā)送者與接收者之間沒有直接引用關(guān)系然遏,發(fā)送請求的對象只需要知道如何發(fā)送請求贫途,而不必知道如何完成請求。那么到底什么是命令模式待侵?
2.1丢早、官方解釋
命令模式(Command Pattern):將一個請求封裝為一個對象,從而讓我們可用不同的請求對客戶進行參數(shù)化秧倾;對請求排隊或者記錄請求日志怨酝,以及支持可撤銷的操作。命令模式是一種對象行為型模式那先,其別名為動作(Action)模式或事務(wù)(Transaction)模式农猬。
2.2、舉個例子
那怎么理解上面比較正式的定義呢售淡?
這里借用劉偉老師的一張圖斤葱。
假設(shè)你新買的200多平米的房子已經(jīng)到手,正準備裝修揖闸,你買了一些開關(guān)揍堕,用于控制新買的電器,比如電燈和排氣扇汤纸。開關(guān)剛買回來的時候衩茸,是不知道具體控制哪個電器的,只有當你用電線將開關(guān)和電器連接起來后蹲嚣,一個開關(guān)才控制了一個具體的電器递瑰。和電燈相連就控制了電燈的開關(guān)祟牲,和排氣扇相連則控制了排氣扇的開關(guān)。
開關(guān)的這種設(shè)計思路其實就是一個很好的命令模式抖部。開關(guān)可以理解為請求的發(fā)送者说贝,電燈和排氣扇則為請求的接受者,不同的請求就可以理解為是連接開關(guān)和電器的不同的電線慎颗。通過這種模式乡恕,開關(guān)(發(fā)送者)就和電器(接收者)松耦合了,只需要更換一下連接的電線(不同的請求)俯萎,就能夠輕松實現(xiàn)同一個開關(guān)(發(fā)送者)控制不同的電器(接收者)傲宜,也就是用不同的請求對客戶進行參數(shù)化。
至于“對請求排隊或者記錄請求日志夫啊,以及支持可撤銷的操作”又當作何理解呢函卒?請求排隊其實就是將很多不同請求放入一個工作隊列中,然后接收者將請求從隊列中一個一個取出去處理撇眯;記錄請求日志报嵌,就是將請求記錄在日志當中,當系統(tǒng)死機后熊榛,可以從日志中取出這些請求锚国,再一個個去處理恢復之前的狀態(tài)。
三玄坦、為什么要用該模式
使用命令模式最重要的原因就是為了解耦血筑,通過引入一個第三方--抽象命令,讓請求者和接收者松耦合煎楣,讓對象之間的調(diào)用關(guān)系更加靈活豺总,這對系統(tǒng)的擴展和維護是有極大好處的。
比如你的豪華大房子又新買了一個電器择懂,恩园欣,就是那種老式吊扇,你想用連接排氣扇的開關(guān)去控制這個吊扇休蟹,怎么辦呢?換根電線將開關(guān)和吊扇連起來就好了日矫。
四赂弓、模式的結(jié)構(gòu)
命令模式的核心在于引入了命令類,通過命令類來降低發(fā)送者和接收者的耦合度哪轿,請求發(fā)送者只需指定一個命令對象盈魁,再通過命令對象來調(diào)用請求接收者的處理方法,其結(jié)構(gòu)如圖所示:
在命令模式結(jié)構(gòu)圖中包含如下幾個角色:
- Command(抽象命令類):抽象命令類一般是一個抽象類或接口窃诉,在其中聲明了用于執(zhí)行請求的execute()等方法杨耙,通過這些方法可以調(diào)用請求接收者的相關(guān)操作赤套。
- ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類,實現(xiàn)了在抽象命令類中聲明的方法珊膜,它對應(yīng)具體的接收者對象容握,將接收者對象的動作綁定其中。在實現(xiàn)execute()方法時车柠,將調(diào)用接收者對象的相關(guān)操作(Action)剔氏。
- Invoker(調(diào)用者):調(diào)用者即請求發(fā)送者,它通過命令對象來執(zhí)行請求竹祷。一個調(diào)用者并不需要在設(shè)計時確定其接收者谈跛,因此它只與抽象命令類之間存在關(guān)聯(lián)關(guān)系。在程序運行時可以將一個具體命令對象注入其中塑陵,再調(diào)用具體命令對象的execute()方法感憾,從而實現(xiàn)間接調(diào)用請求接收者的相關(guān)操作。
- Receiver(接收者):接收者執(zhí)行與請求相關(guān)的操作令花,它具體實現(xiàn)對請求的業(yè)務(wù)處理阻桅。
命令模式的本質(zhì)是對請求進行封裝,一個請求對應(yīng)于一個命令彭则,將發(fā)出命令的責任和執(zhí)行命令的責任分割開鳍刷。每一個命令都是一個操作:請求的一方發(fā)出請求要求執(zhí)行一個操作;接收的一方收到請求俯抖,并執(zhí)行相應(yīng)的操作输瓜。命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口芬萍,更不必知道請求如何被接收尤揣、操作是否被執(zhí)行、何時被執(zhí)行柬祠,以及是怎么被執(zhí)行的北戏。
命令模式的關(guān)鍵在于引入了抽象命令類,請求發(fā)送者針對抽象命令類編程漫蛔,只有實現(xiàn)了抽象命令類的具體命令才與請求接收者相關(guān)聯(lián)嗜愈。在最簡單的抽象命令類中只包含了一個抽象的execute()方法,每個具體命令類將一個Receiver類型的對象作為一個實例變量進行存儲莽龟,從而具體指定一個請求的接收者蠕嫁,不同的具體命令類提供了execute()方法的不同實現(xiàn),并調(diào)用不同接收者的請求處理方法毯盈。
典型的抽象命令類代碼:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:25:14
*
*/
public interface Command {
void execute();
}
對于請求發(fā)送者即調(diào)用者而言剃毒,將針對抽象命令類進行編程,可以通過構(gòu)造注入或者設(shè)值注入的方式在運行時傳入具體命令類對象,并在業(yè)務(wù)方法中調(diào)用命令對象的execute()方法赘阀,其典型代碼:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:24:43
*
*/
public class Invoker {
private Command command;
public Invoker() {
}
public void call() {
command.execute();
}
public void setCommand(Command command) {
this.command = command;
}
}
具體命令類實現(xiàn)了命令類接口益缠,它與請求接收者相關(guān)聯(lián),實現(xiàn)了在抽象命令類中聲明的execute()方法基公,并在實現(xiàn)時調(diào)用接收者的請求響應(yīng)方法action()幅慌,其典型代碼:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:27:56
*
*/
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
請求接收者Receiver類具體實現(xiàn)對請求的業(yè)務(wù)處理,它提供了action()方法酌媒,用于執(zhí)行與請求相關(guān)的操作欠痴,其典型代碼:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:28:45
*
*/
public class Receiver {
public void action() {
System.out.println("--------let us go to play computer game--------");
}
}
五、代碼示例
假設(shè)我們正在開發(fā)一個辦公軟件秒咨,為了給用戶更好的體驗喇辽,打算為這個辦公軟件加一個人性化的設(shè)計,提供一組按鈕雨席,每個按鈕提供三個功能給用戶選擇菩咨,用戶選擇其中一個功能與按鈕綁定,綁定后用戶只要點擊按鈕就能實現(xiàn)想要的功能陡厘。如下:
以Button1為例抽米,可以選擇“關(guān)閉”,“換膚”糙置,“放大”三個其中的一個云茸。如何設(shè)計這個功能呢?
5.1谤饭、不好的設(shè)計
最開始的代碼也許是這樣的:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午2:46:15
*
*/
public class Button1 {
private SkinPeelerHandler handler;
public void onClick() {
handler = new SkinPeelerHandler();
handler.skinPeeler();// 換膚
}
}
上面這段代碼标捺,Button1是invoker(請求發(fā)起者),SkinPeelerHandler是Receiver(請求接收者)揉抵,它們是直接強耦合在一起的亡容,如果想要將Button1同“放大”綁定起來,似乎只能更改Button1的源碼了冤今,這明顯破壞了“開閉原則”闺兢,對用戶來說,完全不具備可操作性戏罢,不靈活不實用屋谭。
也行有人會說,可以給“關(guān)閉”龟糕,“換膚”戴而,“放大”功能設(shè)計一個公共抽象層,然后Button1可以通過與抽象層來打交道翩蘸,這樣就靈活了。是可以的淮逊,但如果是一組毫無關(guān)聯(lián)的接受者呢催首?它們根本無法抽象出一個共同的抽象層來扶踊,這種情況怎么辦呢?
5.2郎任、命令模式設(shè)計
(1)所以用命令模式吧Q砗摹(這里就只列換膚和關(guān)閉兩個功能)
換膚
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午3:56:47
*
*/
public class SkinPeelerHandler {
public void skinPeeler() {
System.out.println("skin peeler");
}
}
關(guān)閉
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午4:02:05
*
*/
public class CloseHandler {
public void close() {
System.out.println("close the software");
}
}
(2)定義抽象命令:
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:25:14
*
*/
public interface Command {
void execute();
}
(3)再定義具體命令:
換膚
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午2:50:45
*
*/
public class SkinPeelerCommand implements Command {
private SkinPeelerHandler handler;
public SkinPeelerCommand(SkinPeelerHandler handler) {
this.handler = handler;
}
@Override
public void execute() {
handler.skinPeeler();
}
}
關(guān)閉
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午4:01:24
*
*/
public class CloseCommand implements Command {
private CloseHandler handler;
private CloseCommand(CloseHandler handler) {
this.handler = handler;
}
@Override
public void execute() {
handler.close();
}
}
(4) 調(diào)用者
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午2:46:35
*
*/
public class Button {
private Command command;
public Button() {
}
public void call() {
command.execute();
}
public void setCommand(Command command) {
this.command = command;
}
}
(5)最后看客戶端
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午4:15:41
*
*/
public class Client {
public static void main(String[] args) {
// 調(diào)用者
Button button = new Button();
// 這里可以通過配置文件獲取具體命令名字,然后通過反射實例化具體命令舶治,這樣更換命令時只需修改配置文件而不需要修改源碼
Command command = new SkinPeelerCommand(new SkinPeelerHandler());
// 將命令傳給invoker
button.setCommand(command);
button.call();
}
}
如果需要修改功能鍵的功能分井,例如某個功能鍵可以實現(xiàn)“打開音樂播放器”,只需要對應(yīng)增加一個新的具體命令類霉猛,在該命令類與“打開音樂播放器請求處理者”(MusicHandler)之間創(chuàng)建一個關(guān)聯(lián)關(guān)系尺锚,然后將該具體命令類的對象通過配置文件注入到某個功能鍵即可,原有代碼無須修改惜浅,符合“開閉原則”瘫辩。在此過程中,每一個具體命令類對應(yīng)一個請求的處理者(接收者)坛悉,通過向請求發(fā)送者(調(diào)用者)注入不同的具體命令對象可以使得相同的發(fā)送者對應(yīng)不同的接收者伐厌,從而實現(xiàn)“將一個請求封裝為一個對象,用不同的請求對客戶進行參數(shù)化”裸影,客戶端只需要將具體命令對象作為參數(shù)注入請求發(fā)送者挣轨,無須直接操作請求的接收者。
5.3轩猩、命令模式中的撤銷
在命令模式中卷扮,可以通過調(diào)用一個命令對象的execute()方法來實現(xiàn)對請求的處理,如果需要撤銷(undo)請求界轩,可通過在命令類中增加一個逆向操作來實現(xiàn)画饥。
除了通過一個逆向操作來實現(xiàn)撤銷(undo)外,還可以通過保存對象的歷史狀態(tài)來實現(xiàn)撤銷浊猾,后者可使用備忘錄模式(Memento Pattern)來實現(xiàn)抖甘。
以控制一個游戲角色向前走為例說明。
(1)抽象command
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午1:25:14
*
*/
public interface Command {
void execute();
void undo();
}
(2)接收者
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午5:14:26
*
*/
public class Role {
public void forward() {
System.out.println("向前走10步葫慎!");
}
public void back() {
System.out.print("后退10步衔彻!");
}
}
(3)具體命令
public class ForwardCommand implements Command {
private Role role;
public ForwardCommand(Role role) {
this.role = role;
}
@Override
public void execute() {
role.forward();
}
@Override
public void undo() {
role.back();
}
}
(4)調(diào)用者
/**
*
* @author w1992wishes
* @created @2017年11月3日-下午4:55:43
*
*/
public class OperatorInterface {
private Command command;
public void operate() {
command.execute();
}
public void reset() {
command.undo();
}
public void setCommand(Command command) {
this.command = command;
}
}
(5)客戶端
public class Client {
public static void main(String[] args) {
OperatorInterface operator = new OperatorInterface();
Command command = new ForwardCommand(new Role());
operator.setCommand(command);
operator.operate();
operator.reset();
}
}
結(jié)果:
向前走10步!
后退10步偷办!
需要注意的是在本實例中只能實現(xiàn)一步撤銷操作艰额,因為沒有保存命令對象的歷史狀態(tài),可以通過引入一個命令集合或其他方式來存儲每一次操作時命令的狀態(tài)椒涯,從而實現(xiàn)多次撤銷操作柄沮。除了Undo操作外,還可以采用類似的方式實現(xiàn)恢復(Redo)操作,即恢復所撤銷的操作(或稱為二次撤銷)祖搓。
5.4狱意、請求日志
請求日志就是將請求的歷史記錄保存下來,通常以日志文件(Log File)的形式永久存儲在計算機中拯欧。很多系統(tǒng)都提供了日志文件详囤,例如Windows日志文件、Oracle日志文件等镐作,日志文件可以記錄用戶對系統(tǒng)的一些操作(例如對數(shù)據(jù)的更改)藏姐。請求日志文件可以實現(xiàn)很多功能,常用功能如下:
- 一旦系統(tǒng)發(fā)生故障该贾,日志文件可以為系統(tǒng)提供一種恢復機制羔杨,在請求日志文件中可以記錄用戶對系統(tǒng)的每一步操作,從而讓系統(tǒng)能夠順利恢復到某一個特定的狀態(tài)靶庙;
- 請求日志也可以用于實現(xiàn)批處理问畅,在一個請求日志文件中可以存儲一系列命令對象,例如一個命令隊列六荒;
- 可以將命令隊列中的所有命令對象都存儲在一個日志文件中护姆,每執(zhí)行一個命令則從日志文件中刪除一個對應(yīng)的命令對象,防止因為斷電或者系統(tǒng)重啟等原因造成請求丟失掏击,而且可以避免重新發(fā)送全部請求時造成某些命令的重復執(zhí)行卵皂,只需讀取請求日志文件,再繼續(xù)執(zhí)行文件中剩余的命令即可砚亭。
這里用一個簡單的日志記錄說明:
(1)請求接收者
/**
* 請求接收者灯变。因為Command依賴Operator,它也將隨Command對象一起序列化捅膘, 所以O(shè)perator也實現(xiàn)Serializable接口
*
* @author w1992wishes
* @created @2017年11月3日-下午5:25:35
*
*/
class Operator implements Serializable {
private static final long serialVersionUID = 4962794574238371441L;
public void insert(String args) {
System.out.println("insert operation: " + args);
}
public void modify(String args) {
System.out.println("update operation: " + args);
}
public void delete(String args) {
System.out.println("delete peration: " + args);
}
}
(2)抽象命令類
/**
* 抽象命令類添祸,由于需要將命令對象寫入文件,因此它實現(xiàn)了Serializable接口寻仗,保證其序列化
*
* @author w1992wishes
* @created @2017年11月3日-下午5:24:04
*
*/
public abstract class Command implements Serializable {
private static final long serialVersionUID = -4023087706968880848L;
protected String name; // 命令名稱
protected String args; // 命令參數(shù)
protected Operator operator; // 維持對接收者對象的引用
public Command(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
/**
* 抽象的執(zhí)行方法execute()刃泌,帶參數(shù)
*
* @param args
*/
public abstract void execute(String args);
/**
* 抽象的執(zhí)行方法execute(),不帶參數(shù)
*
* @param args
*/
public void execute() {
execute(this.args);
}
}
(3)具體命令類
/**
* 具體插入命令類
*
* @author w1992wishes
* @created @2017年11月3日-下午5:26:28
*
*/
class InsertCommand extends Command {
private static final long serialVersionUID = -6239610676788773397L;
public InsertCommand(String name) {
super(name);
}
public void execute(String args) {
this.args = args;
operator.insert(args);
}
}
/**
* 具體刪除命令類
*
* @author w1992wishes
* @created @2017年11月3日-下午5:29:19
*
*/
class DeleteCommand extends Command {
private static final long serialVersionUID = -4259959904986587353L;
public DeleteCommand(String name) {
super(name);
}
public void execute(String args) {
this.args = args;
operator.delete(args);
}
}
/**
* 具體修改命令類
*
* @author w1992wishes
* @created @2017年11月3日-下午5:28:40
*
*/
class ModifyCommand extends Command {
private static final long serialVersionUID = -4259959904986587353L;
public ModifyCommand(String name) {
super(name);
}
public void execute(String args) {
this.args = args;
operator.modify(args);
}
}
(4)請求發(fā)送者
/**
* 請求發(fā)送者
*
* @author w1992wishes
* @created @2017年11月3日-下午5:31:20
*
*/
public class OperatorWindow {
// 定義一個集合來存儲每一次操作時的命令對象
private List<Command> commands = new ArrayList<Command>();
private Command command;
// 設(shè)置具體命令對象
public void setCommand(Command command) {
this.command = command;
}
// 執(zhí)行命令署尤,同時將命令對象添加到命令集合中
public void call(String args) {
command.execute(args);
commands.add(command);
}
// 記錄請求日志耙替,將命令集合寫入日志文件
public void save() {
FileUtil.writeCommands(commands);
}
// 從日志文件中提取命令集合,并調(diào)用所有命令的execute()方法來實現(xiàn)命令的重新執(zhí)行
public void recover() {
List<Command> commands = FileUtil.readCommands();
for (Command command : commands) {
command.execute();
}
}
}
/**
* 文件操作類
*
* @author w1992wishes
* @created @2017年11月3日-下午5:32:16
*
*/
class FileUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);
public static void writeCommands(List<Command> commands) {
try {
FileOutputStream fos = new FileOutputStream("operator.log");
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos));
oos.writeObject(commands);
oos.close();
} catch (Exception e) {
LOGGER.error("writeCommands error!", e);
}
}
public static List<Command> readCommands() {
try {
FileInputStream fis = new FileInputStream("operator.log");
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
@SuppressWarnings("unchecked")
List<Command> commands = (List<Command>) ois.readObject();
ois.close();
return commands;
} catch (Exception e) {
LOGGER.error("readCommands error!", e);
return null;
}
}
}
(5)客戶端
public class Client {
public static void main(String args[]) {
OperatorWindow window = new OperatorWindow(); // 請求發(fā)送者
Command command; // 命令對象
Operator operator = new Operator(); // 請求接收者
// 具體命令
command = new InsertCommand("insert");
command.setOperator(operator);
window.setCommand(command);
window.call("節(jié)點1");
command = new InsertCommand("insert");
command.setOperator(operator);
window.setCommand(command);
window.call("節(jié)點2");
command = new ModifyCommand("modify");
command.setOperator(operator);
window.setCommand(command);
window.call("節(jié)點1");
command = new DeleteCommand("delete");
command.setOperator(operator);
window.setCommand(command);
window.call("節(jié)點2");
System.out.println("---------------------保存操作記錄---------------------");
window.save();
System.out.println("---------------------死機---------------------");
System.out.println("---------------------恢復操作---------------------");
window.recover();
}
}
結(jié)果:
insert operation: 節(jié)點1
insert operation: 節(jié)點2
update operation: 節(jié)點1
delete peration: 節(jié)點2
---------------------保存操作記錄---------------------
---------------------死機---------------------
---------------------恢復操作---------------------
insert operation: 節(jié)點1
insert operation: 節(jié)點2
update operation: 節(jié)點1
delete peration: 節(jié)點2
5.5曹体、請求排隊
其實和請求日志差不多俗扇,就不列代碼了。
六箕别、優(yōu)點和缺點
6.1铜幽、優(yōu)點
- 降低系統(tǒng)的耦合度芽腾。
- 新的命令可以很容易地加入到系統(tǒng)中壮池。
- 可以比較容易地設(shè)計一個命令隊列和宏命令(宏命令又稱為組合命令范舀,它是組合模式和命令模式聯(lián)用的產(chǎn)物南誊。宏命令是一個具體命令類,它擁有一個集合屬性镶殷,在該集合中包含了對其他命令對象的引用。當調(diào)用宏命令的execute()方法時微酬,將遞歸調(diào)用它所包含的每個成員命令的execute()方法绘趋。)。
- 可以方便地實現(xiàn)對請求的Undo和Redo颗管。
6.2陷遮、缺點
使用命令模式可能會導致某些系統(tǒng)有過多的具體命令類。因為針對每一個命令都需要設(shè)計一個具體命令類垦江,因此某些系統(tǒng)可能需要大量具體命令類帽馋,這將影響命令模式的使用。
七比吭、適用環(huán)境
在以下情況下可以使用命令模式:
- 系統(tǒng)需要將請求調(diào)用者和請求接收者解耦绽族,使得調(diào)用者和接收者不直接交互。
- 系統(tǒng)需要在不同的時間指定請求衩藤、將請求排隊和執(zhí)行請求吧慢。
- 系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。
- 系統(tǒng)需要將一組操作組合在一起赏表,即支持宏命令检诗。
八、模式應(yīng)用
- 很多系統(tǒng)都提供了宏命令功能瓢剿,如UNIX平臺下的Shell編程逢慌,可以將多條命令封裝在一個命令對象中,只需要一條簡單的命令即可執(zhí)行一個命令序列间狂,這也是命令模式的應(yīng)用實例之一攻泼。
九、總結(jié)
- 在命令模式中前标,將一個請求封裝為一個對象坠韩,從而使我們可用不同的請求對客戶進行參數(shù)化;對請求排隊或者記錄請求日志炼列,以及支持可撤銷的操作只搁。命令模式是一種對象行為型模式,其別名為動作模式或事務(wù)模式俭尖。
- 命令模式包含四個角色:抽象命令類中聲明了用于執(zhí)行請求的execute()等方法氢惋,通過這些方法可以調(diào)用請求接收者的相關(guān)操作洞翩;具體命令類是抽象命令類的子類,實現(xiàn)了在抽象命令類中聲明的方法焰望,它對應(yīng)具體的接收者對象骚亿,將接收者對象的動作綁定其中;調(diào)用者即請求的發(fā)送者熊赖,又稱為請求者来屠,它通過命令對象來執(zhí)行請求;接收者執(zhí)行與請求相關(guān)的操作震鹉,它具體實現(xiàn)對請求的業(yè)務(wù)處理俱笛。
- 命令模式的本質(zhì)是對命令進行封裝,將發(fā)出命令的責任和執(zhí)行命令的責任分割開传趾。命令模式使請求本身成為一個對象迎膜,這個對象和其他對象一樣可以被存儲和傳遞。
- 命令模式的主要優(yōu)點在于降低系統(tǒng)的耦合度浆兰,增加新的命令很方便磕仅,而且可以比較容易地設(shè)計一個命令隊列和宏命令,并方便地實現(xiàn)對請求的撤銷和恢復簸呈;其主要缺點在于可能會導致某些系統(tǒng)有過多的具體命令類榕订。
- 命令模式適用情況包括:需要將請求調(diào)用者和請求接收者解耦,使得調(diào)用者和接收者不直接交互蝶棋;需要在不同的時間指定請求、將請求排隊和執(zhí)行請求玩裙;需要支持命令的撤銷操作和恢復操作,需要將一組操作組合在一起吃溅,即支持宏命令。