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 Диаграма

Alt Command UML Diagram

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}