Патерны программирования. DesignPatternsphp documentation
Скачать 1.98 Mb.
|
; 32 } 33 34 final public static function get (string $key ) : Service 35 { 36 if ( ! in_array ( $key , self :: $allowedKeys ) || ! isset (self :: $services [ $key ])) { 37 throw new InvalidArgumentException( 'Invalid key given' ); 38 } 39 40 return self :: $services [ $key ]; 41 } 42 } Service.php 1 2 (continues on next page) 74 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 3 namespace DesignPatterns\Structural\Registry; 4 5 class Service 6 { 7 8 } Тест Tests/RegistryTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Registry\Tests; 6 7 use InvalidArgumentException; 8 use DesignPatterns\Structural\Registry\Registry; 9 use DesignPatterns\Structural\Registry\Service; 10 use PHPUnit\Framework\TestCase; 11 12 class RegistryTest extends TestCase 13 { 14 private Service $service ; 15 16 protected function setUp () : void 17 { 18 $this -> service = $this -> getMockBuilder (Service :: class ) -> getMock (); 19 } 20 21 public function testSetAndGetLogger () 22 { 23 Registry :: set (Registry :: LOGGER , $this -> service ); 24 25 $this -> assertSame ( $this -> service , Registry :: get (Registry :: LOGGER )); 26 } 27 28 public function testThrowsExceptionWhenTryingToSetInvalidKey () 29 { 30 $this -> expectException (InvalidArgumentException :: class ); 31 32 Registry :: set ( 'foobar' , $this -> service ); 33 } 34 35 /** 36 * notice @runInSeparateProcess here: without it, a previous test might have set it ␣ ˓→ already and 37 * testing would not be possible. That's why you should implement Dependency ␣ ˓→ Injection where an 38 * injected class may easily be replaced by a mockup (continues on next page) 1.2. Структурные шаблоны проектирования (Structural) 75 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 39 * 40 * @runInSeparateProcess 41 */ 42 public function testThrowsExceptionWhenTryingToGetNotSetKey () 43 { 44 $this -> expectException (InvalidArgumentException :: class ); 45 46 Registry :: get (Registry :: LOGGER ); 47 } 48 } 1.3 Поведенческие шаблоны проектирования (Behavioral) Поведенческие шаблоны проектирования определяют общие закономерности связей между объектами, реализующими данные паттерны. Следование этим шаблонам уменьшает связность системы и облег- чает коммуникацию между объектами, что улучшает гибкость программного продукта. 1.3.1 Цепочка Обязанностей (Chain Of Responsibilities) Назначение Построить цепочку объектов для обработки вызова в последовательном порядке. Если один объект не может справиться с вызовом, он делегирует вызов следующему в цепи и так далее. Примеры • фреймворк для записи журналов, где каждый элемент цепи самостоятельно принимает решение, что делать с сообщением для логирования. • фильтр спама • кеширование: первый объект является экземпляром, к примеру, интерфейса Memcached. Если запись в кеше отсутствует, вызов делегируется интерфейсу базы данных. 76 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Диаграмма UML Код Вы можете найти этот код на GitHub Handler.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\ChainOfResponsibilities; 6 7 use Psr\Http\Message\RequestInterface; 8 9 abstract class Handler 10 { 11 public function __construct ( private ? Handler $successor = null ) 12 { 13 } 14 15 /** 16 * This approach by using a template method pattern ensures you that 17 * each subclass will not forget to call the successor 18 */ 19 final public function handle (RequestInterface $request ) : ? string 20 { 21 $processed = $this -> processing ( $request ); 22 23 if ( $processed === null && $this -> successor !== null ) { 24 // the request has not been processed by this handler => see the next 25 $processed = $this -> successor -> handle ( $request ); 26 } (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 77 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 27 28 return $processed ; 29 } 30 31 abstract protected function processing (RequestInterface $request ) : ? string; 32 } Responsible/FastStorage.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible; 6 7 use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; 8 use Psr\Http\Message\RequestInterface; 9 10 class HttpInMemoryCacheHandler extends Handler 11 { 12 public function __construct ( private array $data , ? Handler $successor = null ) 13 { 14 parent :: __construct ( $successor ); 15 } 16 17 protected function processing (RequestInterface $request ) : ? string 18 { 19 $key = sprintf ( 20 '%s?%s' , 21 $request -> getUri () -> getPath (), 22 $request -> getUri () -> getQuery () 23 ); 24 25 if ( $request -> getMethod () == 'GET' && isset ( $this -> data [ $key ])) { 26 return $this -> data [ $key ]; 27 } 28 29 return null ; 30 } 31 } Responsible/SlowStorage.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible; 6 7 use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; 8 use Psr\Http\Message\RequestInterface; 9 (continues on next page) 78 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 10 class SlowDatabaseHandler extends Handler 11 { 12 protected function processing (RequestInterface $request ) : ? string 13 { 14 // this is a mockup, in production code you would ask a slow (compared to in- ˓→ memory) DB for the results 15 16 return 'Hello World!' ; 17 } 18 } Тест Tests/ChainTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests; 6 7 use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; 8 use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\ ˓→ HttpInMemoryCacheHandler; 9 use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler; 10 use PHPUnit\Framework\TestCase; 11 use Psr\Http\Message\RequestInterface; 12 use Psr\Http\Message\UriInterface; 13 14 class ChainTest extends TestCase 15 { 16 private Handler $chain ; 17 18 protected function setUp () : void 19 { 20 $this -> chain = new HttpInMemoryCacheHandler( 21 [ '/foo/bar?index=1' => 'Hello In Memory!' ], 22 new SlowDatabaseHandler() 23 ); 24 } 25 26 public function testCanRequestKeyInFastStorage () 27 { 28 $uri = $this -> createMock (UriInterface :: class ); 29 $uri -> method ( 'getPath' ) -> willReturn ( '/foo/bar' ); 30 $uri -> method ( 'getQuery' ) -> willReturn ( 'index=1' ); 31 32 $request = $this -> createMock (RequestInterface :: class ); 33 $request -> method ( 'getMethod' ) 34 -> willReturn ( 'GET' ); 35 $request -> method ( 'getUri' ) -> willReturn ( $uri ); (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 79 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 36 37 $this -> assertSame ( 'Hello In Memory!' , $this -> chain -> handle ( $request )); 38 } 39 40 public function testCanRequestKeyInSlowStorage () 41 { 42 $uri = $this -> createMock (UriInterface :: class ); 43 $uri -> method ( 'getPath' ) -> willReturn ( '/foo/baz' ); 44 $uri -> method ( 'getQuery' ) -> willReturn ( '' ); 45 46 $request = $this -> createMock (RequestInterface :: class ); 47 $request -> method ( 'getMethod' ) 48 -> willReturn ( 'GET' ); 49 $request -> method ( 'getUri' ) -> willReturn ( $uri ); 50 51 $this -> assertSame ( 'Hello World!' , $this -> chain -> handle ( $request )); 52 } 53 } 1.3.2 Команда (Command) Назначение Инкапсулировать действие и его параметры Допустим, у нас есть объекты Invoker (Командир) и Receiver (Исполнитель). Этот паттерн использует реализацию интерфейса «Команда», чтобы вызвать некий метод Исполнителя используя для этого из- вестный Командиру метод «execute()». Командир просто знает, что нужно вызвать метод “execute()”, для обработки команды клиента, не разбираясь в деталях реализации Исполнителя. Исполнитель от- делен от Командира. Вторым аспектом этого паттерна является метод undo(), который отменяет действие, выполняемое методом execute(). Команды также могут быть объединены в более общие команды с минимальным копированием-вставкой и полагаясь на композицию поверх наследования. 80 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Примеры • A text editor : all events are commands which can be undone, stacked and saved. • большие утилиты для командной строки (например, Vagrant) используют вложенные команды для разделения различных задач и упаковки их в «модули», каждый из которых может быть реализован с помощью паттерна «Команда». Диаграмма UML 1.3. Поведенческие шаблоны проектирования (Behavioral) 81 DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы также можете найти этот код на GitHub Command.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command; 6 7 interface 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 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command; 6 7 interface 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 2 3 declare (strict_types = 1 ); 4 5 namespace 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 */ 11 class 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 ␣ (continues on next page) 82 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) ˓→ 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 2 3 declare (strict_types = 1 ); 4 5 namespace 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 */ 11 class 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. (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 83 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 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 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command; 6 7 /** 8 * Receiver is a specific service with its own contract and can be only concrete. 9 */ 10 class 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 /** (continues on next page) 84 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 42 * Disable receiver to display message date 43 */ 44 public function disableDate () 45 { 46 $this -> enableDate = false ; 47 } 48 } Invoker.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command; 6 7 /** 8 * Invoker is using the command given to it. 9 * Example : an Application in SF2. 10 */ 11 class 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 } 1.3. Поведенческие шаблоны проектирования (Behavioral) 85 DesignPatternsPHP Documentation, Выпуск 1.0 Тест Tests/CommandTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command\Tests; 6 7 use DesignPatterns\Behavioral\Command\HelloCommand; 8 use DesignPatterns\Behavioral\Command\Invoker; 9 use DesignPatterns\Behavioral\Command\Receiver; 10 use PHPUnit\Framework\TestCase; 11 12 class 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 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Command\Tests; 6 7 use DesignPatterns\Behavioral\Command\AddMessageDateCommand; 8 use DesignPatterns\Behavioral\Command\HelloCommand; |