3.2. 命令行模式
3.2.1. 目的
封装调用与解耦
我们有一个调用器和一个接受器。此模式使用一个【Command】来委托接收器的方法调用,并呈现相同的方法【execute】。调用器只知道调用【execute】来处理客户机的【Command】。从而实现了接收器和调用者的解耦。
此模式的另一个方面是undo(),它取消execute()方法。命令行也可以通过聚合来组合更复杂的命令,使用最小的复制-粘贴,并依赖组合而不是继承。
3.2.2. 例子
文本编辑器:所有事件都可以撤销、堆放、保存的命令。
大型CLI工具使用子命令来分发各种任务,并将它们打包到【模块】,每个模块都可以用命令行模式实现例如【vagrant】。
3.2.3. UML 图

3.2.4. 代码
在 GitHub 上查看代码
Command.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7interface Command
8{
9 /**
10 * this is the most important method in the Command pattern,
11 * The Receiver goes in the constructor.
12 */
13 public function execute();
14}
UndoableCommand.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7interface UndoableCommand extends Command
8{
9 /**
10 * This method is used to undo change made by command execution
11 */
12 public function undo();
13}
HelloCommand.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7/**
8 * This concrete command calls "print" on the Receiver, but an external
9 * invoker just knows that it can call "execute"
10 */
11class HelloCommand implements Command
12{
13 /**
14 * Each concrete command is built with different receivers.
15 * There can be one, many or completely no receivers, but there can be other commands in the parameters
16 */
17 public function __construct(private Receiver $output)
18 {
19 }
20
21 /**
22 * execute and output "Hello World".
23 */
24 public function execute()
25 {
26 // sometimes, there is no receiver and this is the command which does all the work
27 $this->output->write('Hello World');
28 }
29}
AddMessageDateCommand.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7/**
8 * This concrete command tweaks receiver to add current date to messages
9 * invoker just knows that it can call "execute"
10 */
11class AddMessageDateCommand implements UndoableCommand
12{
13 /**
14 * Each concrete command is built with different receivers.
15 * There can be one, many or completely no receivers, but there can be other commands in the parameters.
16 */
17 public function __construct(private Receiver $output)
18 {
19 }
20
21 /**
22 * Execute and make receiver to enable displaying messages date.
23 */
24 public function execute()
25 {
26 // sometimes, there is no receiver and this is the command which
27 // does all the work
28 $this->output->enableDate();
29 }
30
31 /**
32 * Undo the command and make receiver to disable displaying messages date.
33 */
34 public function undo()
35 {
36 // sometimes, there is no receiver and this is the command which
37 // does all the work
38 $this->output->disableDate();
39 }
40}
Receiver.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7/**
8 * Receiver is a specific service with its own contract and can be only concrete.
9 */
10class Receiver
11{
12 private bool $enableDate = false;
13
14 /**
15 * @var string[]
16 */
17 private array $output = [];
18
19 public function write(string $str)
20 {
21 if ($this->enableDate) {
22 $str .= ' [' . date('Y-m-d') . ']';
23 }
24
25 $this->output[] = $str;
26 }
27
28 public function getOutput(): string
29 {
30 return join("\n", $this->output);
31 }
32
33 /**
34 * Enable receiver to display message date
35 */
36 public function enableDate()
37 {
38 $this->enableDate = true;
39 }
40
41 /**
42 * Disable receiver to display message date
43 */
44 public function disableDate()
45 {
46 $this->enableDate = false;
47 }
48}
Invoker.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command;
6
7/**
8 * Invoker is using the command given to it.
9 * Example : an Application in SF2.
10 */
11class Invoker
12{
13 private Command $command;
14
15 /**
16 * in the invoker we find this kind of method for subscribing the command
17 * There can be also a stack, a list, a fixed set ...
18 */
19 public function setCommand(Command $cmd)
20 {
21 $this->command = $cmd;
22 }
23
24 /**
25 * executes the command; the invoker is the same whatever is the command
26 */
27 public function run()
28 {
29 $this->command->execute();
30 }
31}
3.2.5. 测试
Tests/CommandTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command\Tests;
6
7use DesignPatterns\Behavioral\Command\HelloCommand;
8use DesignPatterns\Behavioral\Command\Invoker;
9use DesignPatterns\Behavioral\Command\Receiver;
10use PHPUnit\Framework\TestCase;
11
12class CommandTest extends TestCase
13{
14 public function testInvocation()
15 {
16 $invoker = new Invoker();
17 $receiver = new Receiver();
18
19 $invoker->setCommand(new HelloCommand($receiver));
20 $invoker->run();
21 $this->assertSame('Hello World', $receiver->getOutput());
22 }
23}
Tests/UndoableCommandTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Command\Tests;
6
7use DesignPatterns\Behavioral\Command\AddMessageDateCommand;
8use DesignPatterns\Behavioral\Command\HelloCommand;
9use DesignPatterns\Behavioral\Command\Invoker;
10use DesignPatterns\Behavioral\Command\Receiver;
11use PHPUnit\Framework\TestCase;
12
13class UndoableCommandTest extends TestCase
14{
15 public function testInvocation()
16 {
17 $invoker = new Invoker();
18 $receiver = new Receiver();
19
20 $invoker->setCommand(new HelloCommand($receiver));
21 $invoker->run();
22 $this->assertSame('Hello World', $receiver->getOutput());
23
24 $messageDateCommand = new AddMessageDateCommand($receiver);
25 $messageDateCommand->execute();
26
27 $invoker->run();
28 $this->assertSame("Hello World\nHello World [" . date('Y-m-d') . ']', $receiver->getOutput());
29
30 $messageDateCommand->undo();
31
32 $invoker->run();
33 $this->assertSame("Hello World\nHello World [" . date('Y-m-d') . "]\nHello World", $receiver->getOutput());
34 }
35}