命令行模式(Command)

目的

为了封装调用和解耦。

我们有一个调用程序和一个接收器。 这种模式使用「命令行」将方法调用委托给接收器并且呈现相同的「执行」方法。 因此,调用程序只知道调用「执行」去处理客户端的命令。接收器会从调用程序中分离出来。

这个模式的另一面是取消方法的 execute(),也就是 undo() 。命令行也可以通过最小量的复制粘贴和依赖组合(不是继承)被聚合,从而组合成更复杂的命令集。

例子

  • 文本编辑器:所有事件都是可以被解除、堆放,保存的命令。
  • Symfony2:SF2 命令可以从 CLI 运行,它的建立只需考虑到命令行模式。
  • 大型 CLI 工具使用子程序来分发不同的任务并将它们封装在「模型」中,每个模块都可以通过命令行模式实现(例如:vagrant)。

UML 图

file

代码

你也可以在GitHub上查看 源码

CommandInterface.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Command;
  3. interface CommandInterface
  4. {
  5. /**
  6. * 这是在命令行模式中很重要的方法,
  7. * 这个接收者会被载入构造器
  8. */
  9. public function execute();
  10. }

HelloCommand.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Command;
  3. /**
  4. * 这个具体命令,在接收器上调用 "print" ,
  5. * 但是外部调用者只知道,这个是否可以执行。
  6. */
  7. class HelloCommand implements CommandInterface
  8. {
  9. /**
  10. * @var Receiver
  11. */
  12. private $output;
  13. /**
  14. * 每个具体的命令都来自于不同的接收者。
  15. * 这个可以是一个或者多个接收者,但是参数里必须是可以被执行的命令。
  16. *
  17. * @param Receiver $console
  18. */
  19. public function __construct(Receiver $console)
  20. {
  21. $this->output = $console;
  22. }
  23. /**
  24. * 执行和输出 "Hello World".
  25. */
  26. public function execute()
  27. {
  28. // 有时候,这里没有接收者,并且这个命令执行所有工作。
  29. $this->output->write('Hello World');
  30. }
  31. }

Receiver.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Command;
  3. /**
  4. * 接收方是特定的服务,有自己的 contract ,只能是具体的实例。
  5. */
  6. class Receiver
  7. {
  8. /**
  9. * @var bool
  10. */
  11. private $enableDate = false;
  12. /**
  13. * @var string[]
  14. */
  15. private $output = [];
  16. /**
  17. * @param string $str
  18. */
  19. public function write(string $str)
  20. {
  21. if ($this->enableDate) {
  22. $str .= ' ['.date('Y-m-d').']';
  23. }
  24. $this->output[] = $str;
  25. }
  26. public function getOutput(): string
  27. {
  28. return join("\n", $this->output);
  29. }
  30. /**
  31. * 可以显示消息的时间
  32. */
  33. public function enableDate()
  34. {
  35. $this->enableDate = true;
  36. }
  37. /**
  38. * 禁止显示消息的时间
  39. */
  40. public function disableDate()
  41. {
  42. $this->enableDate = false;
  43. }
  44. }

Invoker.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Command;
  3. /**
  4. *调用者使用这种命令。
  5. * 比例 : 一个在 SF2 中的应用
  6. */
  7. class Invoker
  8. {
  9. /**
  10. * @var CommandInterface
  11. */
  12. private $command;
  13. /**
  14. * 在这种调用者中,我们发现,订阅命令也是这种方法
  15. * 还包括:堆栈、列表、集合等等
  16. *
  17. * @param CommandInterface $cmd
  18. */
  19. public function setCommand(CommandInterface $cmd)
  20. {
  21. $this->command = $cmd;
  22. }
  23. /**
  24. * 执行这个命令;
  25. * 调用者也是用这个命令。
  26. */
  27. public function run()
  28. {
  29. $this->command->execute();
  30. }
  31. }

测试

Tests/CommandTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Command\Tests;
  3. use DesignPatterns\Behavioral\Command\HelloCommand;
  4. use DesignPatterns\Behavioral\Command\Invoker;
  5. use DesignPatterns\Behavioral\Command\Receiver;
  6. use PHPUnit\Framework\TestCase;
  7. class CommandTest extends TestCase
  8. {
  9. public function testInvocation()
  10. {
  11. $invoker = new Invoker();
  12. $receiver = new Receiver();
  13. $invoker->setCommand(new HelloCommand($receiver));
  14. $invoker->run();
  15. $this->assertEquals('Hello World', $receiver->getOutput());
  16. }
  17. }