設(shè)計模式7:命令模式

之前學(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可能會采用這個模式溢十。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垮刹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子张弛,更是在濱河造成了極大的恐慌荒典,老刑警劉巖酪劫,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寺董,居然都是意外死亡覆糟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門遮咖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滩字,“玉大人,你說我怎么就攤上這事御吞÷蠊浚” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵魄藕,是天一觀的道長内列。 經(jīng)常有香客問我,道長背率,這世上最難降的妖魔是什么话瞧? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮寝姿,結(jié)果婚禮上交排,老公的妹妹穿的比我還像新娘。我一直安慰自己饵筑,他們只是感情好埃篓,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著根资,像睡著了一般架专。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上玄帕,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天部脚,我揣著相機與錄音,去河邊找鬼裤纹。 笑死委刘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹰椒。 我是一名探鬼主播锡移,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漆际!你這毒婦竟也來了淆珊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤奸汇,失蹤者是張志新(化名)和其女友劉穎套蒂,沒想到半個月后钞支,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡操刀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了婴洼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骨坑。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖柬采,靈堂內(nèi)的尸體忽然破棺而出欢唾,到底是詐尸還是另有隱情,我是刑警寧澤粉捻,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布礁遣,位于F島的核電站,受9級特大地震影響肩刃,放射性物質(zhì)發(fā)生泄漏祟霍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一盈包、第九天 我趴在偏房一處隱蔽的房頂上張望沸呐。 院中可真熱鬧,春花似錦呢燥、人聲如沸崭添。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呼渣。三九已至,卻和暖如春寞埠,著一層夾襖步出監(jiān)牢的瞬間屁置,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工畸裳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缰犁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓怖糊,卻偏偏與公主長得像帅容,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伍伤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容