Encapsulate a command request as an object to enable, logging and / or queuing of requests, and provides error-handling for unhandled requests.

    将请求封装为对象,从而使你可以用不同的请求对客户进行参数化;对请求「排队」、「记录」、「日志」以及「撤销」、「重做」等操作。

    • 将方法调用、请求或操作封装到单一对象中,从而根据不同的请求对客户进行参数化和传递可执行的方法调用。
    • 背后的主要思想:提供一种职责分离的手段,这些职责包括从执行命令的任意地方发布命令,以及将该职责转而委托给不同的对象。将发出命令和执行命令的责任分开

    动机:命令模式通过将请求本身变成一个对象来使被请求的对象可向未指定的应用对象提出请求。

    适用性:

    • 我们需要抽象出「待执行」的动作以参数化某个对象。从而以「回调」的形式,在需要调用的时候调用。
    • 在不同的时刻「指定」、「排列」和「执行请求」,毕竟一个 Command 对象可以由一个与初始请求无关的生存期。
    • 支持 取消操作,在执行操作 Execute 之前,能够将实行操作之前的状态暂存起来,在取消操作时这个状态恢复过来,配合一个历史列表以实现取消和重做。
    • 支持修改日志,这样当系统崩溃之时,这些修改能够从日志里面提取出来重做一次。
    • 能够在「原语操作」上的高层操作构造一个系统。这样一种结构在支持「事务」即 transaction 的信息系统之中很常见。一组事务封装了对数据的一组变动。Command 模式提供了对事务进行建模的方法。Command 提供一个公共的接口,使得可以以同一种方式调用所有的事务。同时也能够使得添加新的事务以扩展系统。

    代码结构

    在 Tomcat 中,Command 的设计模式就能够有所体现:

    Connector 负责创建类似于 HTTPProcessor / TCPProcessor / UDPProcessor 的命令,而最终的负责处理的是实现了对应命令的接口的 Container,如 Engine / Host / Context / Wrapper 构成的责任链中具体的容器来处理。

    具体 Case:

    1. // Client
    2. class Application {
    3. private _document: Document;
    4. private _menu: Menu;
    5. addDoc(document: Document): this;
    6. createMenu() {
    7. this._menu = new Menu(this._document);
    8. this._menu.addMenuItem('paste');
    9. this._menu.addMenuItem('copy');
    10. }
    11. }
    12. // Receiver
    13. class Document {
    14. open();
    15. close();
    16. paste();
    17. copy();
    18. }
    19. // Invoker
    20. class Menu {
    21. private _menuList: MenuItem[];
    22. private _document: Document;
    23. constructor(doc: Document) {
    24. this._document = doc;
    25. }
    26. addMenuItem(type: string) {
    27. let command = null;
    28. if (type === 'copy') {
    29. command = new CopyCommand(this._document);
    30. } else if (type === 'paste') {
    31. command = new PasteCommand(this._document);
    32. } else {
    33. command = new EditMacroCommand(this._document);
    34. }
    35. this._menuList.push(new MenuItem(this._document, command));
    36. };
    37. }
    38. // Invoker
    39. class MenuItem {
    40. private _command: Command;
    41. constructor(document: Document) {
    42. this._command = new CopyCommand(document);
    43. }
    44. onClick() {
    45. this._command.execute();
    46. }
    47. }
    48. // Command
    49. class Command {
    50. private _document: Document;
    51. execute(): this;
    52. }
    53. // Concrete Command
    54. class CopyCommand extends Command {
    55. execute() {
    56. this._document.copy();
    57. }
    58. }
    59. // Concrete Command
    60. class PasteCommand extends Command {
    61. execute() {
    62. this._document.paste();
    63. }
    64. }
    65. // Concrete Command
    66. class EditMacroCommand extends Command {
    67. private _commands: Command[];
    68. execute() {
    69. this._commands.forEach(c => c.execute());
    70. }
    71. }

    我们可以在 Command 层面上动态地复用或者插入对应的 Command 来实现代码层面上的复用,或者组合几个子 Command 为更为复杂的 Command 对象,因而在处理提交过来的请求的时候我们只需要知道如何提交它而不用关心具体的处理细节。

    实现的时候我们应该考虑到:

    • 一个命令对象应该到达何种「智能程度」。命令对象的能力的粒度。
    • 支持「取消」和「重做」。
      • 接收者对象,真正执行处理该请求的各种操作以及接收者上执行的操作参数。
      • 改变值存储于命令的历史列表中。

    效果:

    • 结构调用操作的对象和具体实现操作的对象的解耦。(发送者和接收者的解耦合 anInvoker => aCommand => aReceiver
    • Command 是头等对象,可以像其他对象一样被操纵和扩展。
    • 可以实现复合命令。
    • 使得扩展新的命令更为容易。