之前學(xué)習(xí)過的設(shè)計模式灭返,有對行為的封裝(策略模式)料按,有對創(chuàng)建的封裝(工廠方法和抽象工廠)篡殷,今天要學(xué)的命令模式(Command DP)則是對請求(request)的封裝。
或許有人會覺得請求也是一種行為藻治,但兩個概念還是大不相同。行為只是一個寬泛的概念巷挥,而請求就具體得多桩卵。
把請求抽象成命令類,是命令模式的靈魂。請求本身就可以看做一個命令(Command)雏节,由調(diào)用者(Invoker)發(fā)出胜嗓,然后還得有目標(biāo)(Target)來接受這個命令。
此外钩乍,因為有了具體的命令辞州,所以可以和隊列配合使用,還可以撤銷(Undo)和記錄(Log)寥粹。
代碼:
抽象類Command变过,在執(zhí)行以外,還能撤銷和恢復(fù):
/**
*
* Interface for Commands.
*
*/
public abstract class Command {
public abstract void execute(Target target);
public abstract void undo();
public abstract void redo();
@Override
public abstract String toString();
}
有一個巫師(Wizard)作為調(diào)用者涝涤,會施法:
/**
*
* Wizard is the invoker of the commands
*
*/
public class Wizard {
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
private Deque<Command> undoStack = new LinkedList<>();
private Deque<Command> redoStack = new LinkedList<>();
public Wizard() {}
/**
* Cast spell
*/
public void castSpell(Command command, Target target) {
LOGGER.info("{} casts {} at {}", this, command, target);
command.execute(target);
undoStack.offerLast(command);
}
/**
* Undo last spell
*/
public void undoLastSpell() {
if (!undoStack.isEmpty()) {
Command previousSpell = undoStack.pollLast();
redoStack.offerLast(previousSpell);
LOGGER.info("{} undoes {}", this, previousSpell);
previousSpell.undo();
}
}
/**
* Redo last spell
*/
public void redoLastSpell() {
if (!redoStack.isEmpty()) {
Command previousSpell = redoStack.pollLast();
undoStack.offerLast(previousSpell);
LOGGER.info("{} redoes {}", this, previousSpell);
previousSpell.redo();
}
}
@Override
public String toString() {
return "Wizard";
}
}
注意這里面undo和redo的實現(xiàn)媚狰。
首先是有兩個Deque,undoStack是用來保存命令的歷史阔拳,當(dāng)要求撤銷的時候崭孤,彈出上一個命令,然后做撤銷糊肠。只是光這樣還不夠辨宠,因為還得支持redo。
redo就相當(dāng)于對undo的undo(當(dāng)然具體看實現(xiàn)货裹,也不一定就是取消undo嗤形,不過從概念上來講是這樣,就好比這里的undo也不一定就是撤銷泪酱,因為由具體的Command來決定)派殷,也就是說redoStack保存的是執(zhí)行undo的命令的歷史,當(dāng)要redo上一個undo的時候墓阀,彈出該命令然后執(zhí)行redo操作毡惜。然而還沒完,這個操作應(yīng)該也要能被undo斯撮,因此又把該命令加入undoStack经伙。
具體的命令類:
/**
*
* Enumeration for target visibility.
*
*/
public enum Visibility {
VISIBLE("visible"), INVISIBLE("invisible"), UNDEFINED("");
private String title;
Visibility(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
/**
*
* InvisibilitySpell is a concrete command
*
*/
public class InvisibilitySpell extends Command {
private Target target;
@Override
public void execute(Target target) {
target.setVisibility(Visibility.INVISIBLE);
this.target = target;
}
@Override
public void undo() {
if (target != null) {
target.setVisibility(Visibility.VISIBLE);
}
}
@Override
public void redo() {
if (target != null) {
target.setVisibility(Visibility.INVISIBLE);
}
}
@Override
public String toString() {
return "Invisibility spell";
}
}
/**
*
* Enumeration for target size.
*
*/
public enum Size {
SMALL("small"), NORMAL("normal"), LARGE("large"), UNDEFINED("");
private String title;
Size(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
/**
*
* ShrinkSpell is a concrete command
*
*/
public class ShrinkSpell extends Command {
private Size oldSize;
private Target target;
@Override
public void execute(Target target) {
oldSize = target.getSize();
target.setSize(Size.SMALL);
this.target = target;
}
@Override
public void undo() {
if (oldSize != null && target != null) {
Size temp = target.getSize();
target.setSize(oldSize);
oldSize = temp;
}
}
@Override
public void redo() {
undo();
}
@Override
public String toString() {
return "Shrink spell";
}
}
抽象target類:
/**
*
* Base class for spell targets.
*
*/
public abstract class Target {
private static final Logger LOGGER = LoggerFactory.getLogger(Target.class);
private Size size;
private Visibility visibility;
public Size getSize() {
return size;
}
public void setSize(Size size) {
this.size = size;
}
public Visibility getVisibility() {
return visibility;
}
public void setVisibility(Visibility visibility) {
this.visibility = visibility;
}
@Override
public abstract String toString();
/**
* Print status
*/
public void printStatus() {
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
}
}
具體的target類:
/**
*
* Goblin is the target of the spells
*
*/
public class Goblin extends Target {
public Goblin() {
setSize(Size.NORMAL);
setVisibility(Visibility.VISIBLE);
}
@Override
public String toString() {
return "Goblin";
}
}
測試:
public static void main(String[] args) {
Wizard wizard = new Wizard();
Goblin goblin = new Goblin();
goblin.printStatus();
wizard.castSpell(new ShrinkSpell(), goblin);
goblin.printStatus();
wizard.castSpell(new InvisibilitySpell(), goblin);
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
}
}
可以看到,巫師可以使用兩個不同的法術(shù)勿锅,而且還可以undo和redo帕膜,并且不用知道之前用的是什么法術(shù)。
個人覺得很多IDE可能會采用這個模式溢十。