PHP 命令行模式
目的
封装调用与解耦
我们有一个调用器和一个接受器。此模式使用一个【Command】来委托接收器的方法调用,并呈现相同的方法【execute】。调用器只知道调用【execute】来处理客户机的【Command】。从而实现了接收器和调用者的解耦。
此模式的另一个方面是undo(),它取消execute()方法。命令行也可以通过聚合来组合更复杂的命令,使用最小的复制-粘贴,并依赖组合而不是继承。
例子
- 文本编辑器:所有事件都可以撤销、堆放、保存的命令。
- 大型CLI工具使用子命令来分发各种任务,并将它们打包到【模块】,每个模块都可以用命令行模式实现例如【vagrant】。
UML 图
代码
Command.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; interface Command { /** * this is the most important method in the Command pattern, * The Receiver goes in the constructor. */ public function execute(); }
UndoableCommand.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; interface UndoableCommand extends Command { /** * This method is used to undo change made by command execution */ public function undo(); }
HelloCommand.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; /** * This concrete command calls "print" on the Receiver, but an external * invoker just knows that it can call "execute" */ class HelloCommand implements Command { /** * Each concrete command is built with different receivers. * There can be one, many or completely no receivers, but there can be other commands in the parameters */ public function __construct(private Receiver $output) { } /** * execute and output "Hello World". */ public function execute() { // sometimes, there is no receiver and this is the command which does all the work $this->output->write('Hello World'); } }
AddMessageDateCommand.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; /** * This concrete command tweaks receiver to add current date to messages * invoker just knows that it can call "execute" */ class AddMessageDateCommand implements UndoableCommand { /** * Each concrete command is built with different receivers. * There can be one, many or completely no receivers, but there can be other commands in the parameters. */ public function __construct(private Receiver $output) { } /** * Execute and make receiver to enable displaying messages date. */ public function execute() { // sometimes, there is no receiver and this is the command which // does all the work $this->output->enableDate(); } /** * Undo the command and make receiver to disable displaying messages date. */ public function undo() { // sometimes, there is no receiver and this is the command which // does all the work $this->output->disableDate(); } }
Receiver.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; /** * Receiver is a specific service with its own contract and can be only concrete. */ class Receiver { private bool $enableDate = false; /** * @var string[] */ private array $output = []; public function write(string $str) { if ($this->enableDate) { $str .= ' [' . date('Y-m-d') . ']'; } $this->output[] = $str; } public function getOutput(): string { return join("\n", $this->output); } /** * Enable receiver to display message date */ public function enableDate() { $this->enableDate = true; } /** * Disable receiver to display message date */ public function disableDate() { $this->enableDate = false; } }
Invoker.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command; /** * Invoker is using the command given to it. * Example : an Application in SF2. */ class Invoker { private Command $command; /** * in the invoker we find this kind of method for subscribing the command * There can be also a stack, a list, a fixed set ... */ public function setCommand(Command $cmd) { $this->command = $cmd; } /** * executes the command; the invoker is the same whatever is the command */ public function run() { $this->command->execute(); } }
测试
Tests/CommandTest.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command\Tests; use DesignPatterns\Behavioral\Command\HelloCommand; use DesignPatterns\Behavioral\Command\Invoker; use DesignPatterns\Behavioral\Command\Receiver; use PHPUnit\Framework\TestCase; class CommandTest extends TestCase { public function testInvocation() { $invoker = new Invoker(); $receiver = new Receiver(); $invoker->setCommand(new HelloCommand($receiver)); $invoker->run(); $this->assertSame('Hello World', $receiver->getOutput()); } }
Tests/UndoableCommandTest.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Command\Tests; use DesignPatterns\Behavioral\Command\AddMessageDateCommand; use DesignPatterns\Behavioral\Command\HelloCommand; use DesignPatterns\Behavioral\Command\Invoker; use DesignPatterns\Behavioral\Command\Receiver; use PHPUnit\Framework\TestCase; class UndoableCommandTest extends TestCase { public function testInvocation() { $invoker = new Invoker(); $receiver = new Receiver(); $invoker->setCommand(new HelloCommand($receiver)); $invoker->run(); $this->assertSame('Hello World', $receiver->getOutput()); $messageDateCommand = new AddMessageDateCommand($receiver); $messageDateCommand->execute(); $invoker->run(); $this->assertSame("Hello World\nHello World [" . date('Y-m-d') . ']', $receiver->getOutput()); $messageDateCommand->undo(); $invoker->run(); $this->assertSame("Hello World\nHello World [" . date('Y-m-d') . "]\nHello World", $receiver->getOutput()); } }