3.2. Команда
3.2.1. Предназначение
За капсулиране на извикване и отделяне.
Ние имаме Повикващ и Получател. Този модел използва „Command“, за да делегира извикването на метода срещу Получателя и представя същия метод „execute“. Следователно Повикващ просто знае да извика „execute“, за да обработи командата на клиента. Получателят е отделен от Повикващия.
Вторият аспект на този модел е undo(), който отменя метода execute(). Command може да се агрегира и комбинира по-сложни команди с минимално copy&paste и разчитане на композиция над наследяване.
3.2.2. Примери
A text editor : all events are commands which can be undone, stacked and saved.
големите 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}