模式定義
命令模式(Command),將一個請求封裝為一個對象顶捷,從而使你可用不同的請求對客戶進(jìn)行參數(shù)化;對請求排隊或記錄請求日志,以及支持可撤銷的操作键畴。
類圖
Command:用來聲明執(zhí)行操作的接口。
abstract class Command {
Receiver receiver;
public Command(Receiver receiver){
this.receiver = receiver;
}
abstract void execute();
}
ConcreteCommand:將一個接收者對象綁定于一個動作突雪,調(diào)用接收者相應(yīng)的操作起惕,以實(shí)現(xiàn)Execute。
class ConcreteCommand extends Command {
public ConcreteCommand(Receiver receiver){
super(receiver);
}
@Override
void execute() {
receiver.action();
}
}
Invoker:要求該命令執(zhí)行這個請求咏删。
class Invoker {
Command command;
public void setCommand(Command command) {
this.command = command;
}
public void ExecuteCommand(){
command.execute();
}
}
Receiver:知道如何實(shí)施與執(zhí)行一個與請求相關(guān)的操作惹想,任何類都可能作為一個接收者。
class Receiver {
public void action(){
System.out.println("執(zhí)行請求督函!");
}
}
客戶端代碼:
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.ExecuteCommand();
}
撤銷和重做
如果需要有命令撤銷和重做嘀粱,Command接口不止需要execute()方法,還要有undo()方法辰狡。
abstract class Command {
//執(zhí)行命令和重做
abstract void execute();
//撤銷
abstract void undo();
}
如果需要實(shí)現(xiàn)多次撤銷重做锋叨,需要調(diào)用方維護(hù)undo和redo兩個盛放Command的棧(用List實(shí)現(xiàn)),首次執(zhí)行一個Command時宛篇,執(zhí)行execute()并將其放入undo棧內(nèi)娃磺,同時要清空redo棧;當(dāng)執(zhí)行撤銷操作時把undo棧內(nèi)最上面一個Command拿出來執(zhí)行undo()叫倍,然后將其放入redo棧內(nèi)偷卧;執(zhí)行重做操作時把redo棧內(nèi)最上面一個Command拿出來執(zhí)行execute(),然后將其放入undo棧內(nèi)段标。
示例代碼:
class Invoker {
//可撤銷的步數(shù)
int undoCount;
List<Command> undoList = new ArrayList();
List<Command> redoList = new ArrayList();
public Invoker(){
undoCount = 5;
}
/**
* 執(zhí)行新操作
* @param command
*/
public void executeCommand(Command command){
command.execute();
//保留最近undoCount次操作涯冠,刪除最早操作
if (undoList.size() >= undoCount){
undoList.remove(0);
}
undoList.add(command);
//執(zhí)行新操作后清空redoList,因為這些操作不能恢復(fù)了
redoList.clear();
}
/**
* 執(zhí)行撤銷
*/
public void undo(){
if (undoList.size() <= 0){
return;
}
//undoList中出棧第一個執(zhí)行
Command command = undoList.get(undoList.size() - 1);
command.undo();
undoList.remove(command);
redoList.add(command);
}
/**
* 執(zhí)行重做
*/
public void redo(){
if (redoList.size() <= 0){
return;
}
//redoList中出棧第一個執(zhí)行
Command command = redoList.get(redoList.size() - 1);
command.execute();
redoList.remove(command);
undoList.add(command);
}
}
如果命令棧中存放很多Command對象會占用大量內(nèi)存逼庞,所以用undoCount做限制蛇更。
應(yīng)用場景
- 系統(tǒng)需要將請求調(diào)用者和請求接收者解耦,使得調(diào)用者和接收者不直接交互。
- 系統(tǒng)需要在不同的時間指定請求派任、將請求排隊和執(zhí)行請求砸逊。
- 系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作。
- 系統(tǒng)需要將一組操作組合在一起掌逛,即支持宏命令师逸。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 降低對象之間的耦合度。
- 新的命令可以很容易地加入到系統(tǒng)中豆混。
- 可以比較容易地設(shè)計一個組合命令篓像。
缺點(diǎn)
- 使用命令模式可能會導(dǎo)致某些系統(tǒng)有過多的具體命令類。因為針對每一個命令都需要設(shè)計一個具體命令類皿伺,因此某些系統(tǒng)可能需要大量具體命令類员辩。
JDK中的命令模式
java.lang.Runnable
看下示例代碼
MyRunnable:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("開始執(zhí)行");
}
}
main:
public static void main(String[] args) {
//相當(dāng)與命令模式的Command command = new ConcreteCommand();
MyRunnable myRunnable = new MyRunnable();
//相當(dāng)與命令模式的new Invoker()后執(zhí)行invoker.setCommand(command)
Thread thread = new Thread(myRunnable);
thread.start();
}
這段代碼中,
Runnable相當(dāng)于抽象命令接口Command鸵鸥,run()方法相當(dāng)于Command的execute()奠滑;
MyRunnable對象相當(dāng)于具體命令對象ConcreteCommand;
Thread對象對應(yīng)命令模式的Invoker,new Thread(myRunnable)相當(dāng)于通過setCommand方法傳入命令對象妒穴。
你可能會說沒有找到Receiver宋税,因為MyRunnable已經(jīng)擁有要執(zhí)行的具體邏輯了,這里不需要接收者讼油。
命令模式與策略模式的區(qū)別
- 目標(biāo)不同:策略模式主要針對統(tǒng)一動作杰赛,采用不同的實(shí)現(xiàn)方式;而命令模式針對不同命令汁讼。
- 打個比方:命令模式相當(dāng)于菜單里的復(fù)制淆攻、移動、壓縮等嘿架;而策略模式是其中一個功能比如:壓縮瓶珊,的不同種實(shí)現(xiàn)。