Патерны программирования. DesignPatternsphp documentation
Скачать 1.98 Mb.
|
$user ) 16 { 17 $this -> ui -> outputUserInfo ( $user ); 18 } 19 20 public function getUser (string $username ) : string 21 { 22 return $this -> userRepository -> getUserName ( $username ); 23 } 24 } Тест Tests/MediatorTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Tests\Mediator\Tests; 6 7 use DesignPatterns\Behavioral\Mediator\Ui; (continues on next page) 100 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 8 use DesignPatterns\Behavioral\Mediator\UserRepository; 9 use DesignPatterns\Behavioral\Mediator\UserRepositoryUiMediator; 10 use PHPUnit\Framework\TestCase; 11 12 class MediatorTest extends TestCase 13 { 14 public function testOutputHelloWorld () 15 { 16 $mediator = new UserRepositoryUiMediator( new UserRepository(), new Ui()); 17 18 $this -> expectOutputString ( 'User: Dominik' ); 19 $mediator -> printInfoAbout ( 'Dominik' ); 20 } 21 } 1.3.6 Хранитель (Memento) Назначение Шаблон предоставляет возможность восстановить объект в его предыдущем состоянии (отменить дей- ствие посредством отката к предыдущему состоянию) или получить доступ к состоянию объекта, не раскрывая его реализацию (т.е. сам объект не обязан иметь функциональность для возврата текущего состояния). Шаблон Хранитель реализуется тремя объектами: «Создателем» (originator), «Опекуном» (caretaker) и «Хранитель» (memento). Хранитель - это объект, который хранит конкретный снимок состояния некоторого объекта или ре- сурса: строки, числа, массива, экземпляра класса и так далее. Уникальность в данном случае подра- зумевает не запрет на существование одинаковых состояний в разных снимках, а то, что состояние можно извлечь в виде независимой копии. Любой объект, сохраняемый в Хранителе, должен быть полной копией исходного объекта, а не ссылкой на исходный объект. Сам объект Хранитель является «непрозрачным объектом» (тот, который никто не может и не должен изменять). Создатель — это объект, который содержит в себе актуальное состояние внешнего объекта строго заданного типа и умеет создавать уникальную копию этого состояния, возвращая её, обёрнутую в объ- ект Хранителя. Создатель не знает истории изменений. Создателю можно принудительно установить конкретное состояние извне, которое будет считаться актуальным. Создатель должен позаботиться о том, чтобы это состояние соответствовало типу объекта, с которым ему разрешено работать. Создатель может (но не обязан) иметь любые методы, но они не могут менять сохранённое состояние объекта. Опекун управляет историей снимков состояний. Он может вносить изменения в объект, принимать решение о сохранении состояния внешнего объекта в Создателе, запрашивать от Создателя снимок текущего состояния, или привести состояние Создателя в соответствие с состоянием какого-то снимка из истории. 1.3. Поведенческие шаблоны проектирования (Behavioral) 101 DesignPatternsPHP Documentation, Выпуск 1.0 Примеры • Зерно генератора псевдослучайных чисел. • Состояние конечного автомата • Контроль промежуточных состояний модели в ORM перед сохранением Диаграмма UML 102 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы можете найти этот код на GitHub Memento.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Memento; 6 7 class Memento 8 { 9 public function __construct ( private State $state ) 10 { 11 } 12 13 public function getState () : State 14 { 15 return $this -> state ; 16 } 17 } State.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Memento; 6 7 use InvalidArgumentException; 8 9 class State implements \Stringable 10 { 11 public const STATE_CREATED = 'created' ; 12 public const STATE_OPENED = 'opened' ; 13 public const STATE_ASSIGNED = 'assigned' ; 14 public const STATE_CLOSED = 'closed' ; 15 16 private string $state ; 17 18 /** 19 * @var string[] 20 */ 21 private static array $validStates = [ 22 self :: STATE_CREATED , 23 self :: STATE_OPENED , 24 self :: STATE_ASSIGNED , 25 self :: STATE_CLOSED , 26 ]; 27 28 public function __construct (string $state ) (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 103 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 29 { 30 self :: ensureIsValidState ( $state ); 31 32 $this -> state = $state ; 33 } 34 35 private static function ensureIsValidState (string $state ) 36 { 37 if ( ! in_array ( $state , self :: $validStates )) { 38 throw new InvalidArgumentException( 'Invalid state given' ); 39 } 40 } 41 42 public function __toString () : string 43 { 44 return $this -> state ; 45 } 46 } Ticket.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Memento; 6 7 /** 8 * Ticket is the "Originator" in this implementation 9 */ 10 class Ticket 11 { 12 private State $currentState ; 13 14 public function __construct () 15 { 16 $this -> currentState = new State(State :: STATE_CREATED ); 17 } 18 19 public function open () 20 { 21 $this -> currentState = new State(State :: STATE_OPENED ); 22 } 23 24 public function assign () 25 { 26 $this -> currentState = new State(State :: STATE_ASSIGNED ); 27 } 28 29 public function close () 30 { 31 $this -> currentState = new State(State :: STATE_CLOSED ); (continues on next page) 104 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 32 } 33 34 public function saveToMemento () : Memento 35 { 36 return new Memento( clone $this -> currentState ); 37 } 38 39 public function restoreFromMemento (Memento $memento ) 40 { 41 $this -> currentState = $memento -> getState (); 42 } 43 44 public function getState () : State 45 { 46 return $this -> currentState ; 47 } 48 } Тест Tests/MementoTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Memento\Tests; 6 7 use DesignPatterns\Behavioral\Memento\State; 8 use DesignPatterns\Behavioral\Memento\Ticket; 9 use PHPUnit\Framework\TestCase; 10 11 class MementoTest extends TestCase 12 { 13 public function testOpenTicketAssignAndSetBackToOpen () 14 { 15 $ticket = new Ticket(); 16 17 // open the ticket 18 $ticket -> open (); 19 $openedState = $ticket -> getState (); 20 $this -> assertSame (State :: STATE_OPENED , (string) $ticket -> getState ()); 21 22 $memento = $ticket -> saveToMemento (); 23 24 // assign the ticket 25 $ticket -> assign (); 26 $this -> assertSame (State :: STATE_ASSIGNED , (string) $ticket -> getState ()); 27 28 // now restore to the opened state, but verify that the state object has been ␣ ˓→ cloned for the memento (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 105 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 29 $ticket -> restoreFromMemento ( $memento ); 30 31 $this -> assertSame (State :: STATE_OPENED , (string) $ticket -> getState ()); 32 $this -> assertNotSame ( $openedState , $ticket -> getState ()); 33 } 34 } 1.3.7 Объект Null (Null Object) Назначение NullObject не шаблон из книги Банды Четырёх, но схема, которая появляется достаточно часто, чтобы считаться паттерном. Она имеет следующие преимущества: • Клиентский код упрощается • Уменьшает шанс исключений из-за нулевых указателей (и ошибок PHP различного уровня) • Меньше дополнительных условий — значит меньше тесткейсов Методы, которые возвращают объект или Null, вместо этого должны вернуть объект NullObject. Это упрощённый формальный код, устраняющий необходимость проверки if (!is_null($obj)) { $obj->callSomething(); }, заменяя её на обычный вызов $obj->callSomething();. Примеры • Null logger or null output to preserve a standard way of interaction between objects, even if the shouldn’t do anything • null handler in a Chain of Responsibilities pattern • null command in a Command pattern 106 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Диаграмма UML 1.3. Поведенческие шаблоны проектирования (Behavioral) 107 DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы можете найти этот код на GitHub Service.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\NullObject; 6 7 class Service 8 { 9 public function __construct ( private Logger $logger ) 10 { 11 } 12 13 /** 14 * do something ... 15 */ 16 public function doSomething () 17 { 18 // notice here that you don't have to check if the logger is set with eg. is_ ˓→ null(), instead just use it 19 $this -> logger -> log ( 'We are in ' __METHOD__ ); 20 } 21 } Logger.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\NullObject; 6 7 /** 8 * Key feature: NullLogger must inherit from this interface like any other loggers 9 */ 10 interface Logger 11 { 12 public function log (string $str ); 13 } PrintLogger.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\NullObject; 6 7 class PrintLogger implements Logger (continues on next page) 108 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 8 { 9 public function log (string $str ) 10 { 11 echo $str ; 12 } 13 } NullLogger.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\NullObject; 6 7 class NullLogger implements Logger 8 { 9 public function log (string $str ) 10 { 11 // do nothing 12 } 13 } Тест Tests/LoggerTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\NullObject\Tests; 6 7 use DesignPatterns\Behavioral\NullObject\NullLogger; 8 use DesignPatterns\Behavioral\NullObject\PrintLogger; 9 use DesignPatterns\Behavioral\NullObject\Service; 10 use PHPUnit\Framework\TestCase; 11 12 class LoggerTest extends TestCase 13 { 14 public function testNullObject () 15 { 16 $service = new Service( new NullLogger()); 17 $this -> expectOutputString ( '' ); 18 $service -> doSomething (); 19 } 20 21 public function testStandardLogger () 22 { 23 $service = new Service( new PrintLogger()); 24 $this -> expectOutputString ( 'We are in DesignPatterns\Behavioral\NullObject\ (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 109 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) ˓→ Service::doSomething' ); 25 $service -> doSomething (); 26 } 27 } 1.3.8 Наблюдатель (Observer) Назначение Для реализации публикации/подписки на поведение объекта, всякий раз, когда объект «Subject» ме- няет свое состояние, прикрепленные объекты «Observers» будут уведомлены. Паттерн используется, чтобы сократить количество связанных напрямую объектов и вместо этого использует слабую связь (loose coupling). Примеры • Система очереди сообщений наблюдает за очередями, чтобы отображать прогресс в GUI Примечание PHP предоставляет два стандартных интерфейса, которые могут помочь реализовать этот шаблон: SplObserver и SplSubject. Диаграмма UML 110 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы можете найти этот код на GitHub User.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Observer; 6 7 use SplSubject; 8 use SplObjectStorage; 9 use SplObserver; 10 11 /** 12 * User implements the observed object (called Subject), it maintains a list of ␣ ˓→ observers and sends notifications to 13 * them in case changes are made on the User object 14 */ 15 class User implements SplSubject 16 { 17 private SplObjectStorage $observers ; 18 private ; 19 20 public function __construct () 21 { 22 $this -> observers = new SplObjectStorage(); 23 } 24 25 public function attach (SplObserver $observer ) : void 26 { 27 $this -> observers -> attach ( $observer ); 28 } 29 30 public function detach (SplObserver $observer ) : void 31 { 32 $this -> observers -> detach ( $observer ); 33 } 34 35 public function changeEmail (string ) : void 36 { 37 $this -> = ; 38 $this -> notify (); 39 } 40 41 public function notify () : void 42 { 43 /** @var SplObserver $observer */ 44 foreach ( $this -> observers as $observer ) { 45 $observer -> update ( $this ); 46 } 47 } (continues on next page) 1.3. Поведенческие шаблоны проектирования (Behavioral) 111 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 48 } UserObserver.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Observer; 6 7 use SplObserver; 8 use SplSubject; 9 10 class UserObserver implements SplObserver 11 { 12 /** 13 * @var SplSubject[] 14 */ 15 private array $changedUsers = []; 16 17 /** 18 * It is called by the Subject, usually by SplSubject::notify() 19 */ 20 public function update (SplSubject $subject ) : void 21 { 22 $this -> changedUsers [] = clone $subject ; 23 } 24 25 /** 26 * @return SplSubject[] 27 */ 28 public function getChangedUsers () : array 29 { 30 return $this -> changedUsers ; 31 } 32 } Тест Tests/ObserverTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Observer\Tests; 6 7 use DesignPatterns\Behavioral\Observer\User; 8 use DesignPatterns\Behavioral\Observer\UserObserver; 9 use PHPUnit\Framework\TestCase; 10 (continues on next page) 112 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 11 class ObserverTest extends TestCase 12 { 13 public function testChangeInUserLeadsToUserObserverBeingNotified () 14 { 15 $observer = new UserObserver(); 16 17 $user = new User(); 18 $user -> attach ( $observer ); 19 20 $user -> changeEmail ( 'foo@bar.com' ); 21 $this -> assertCount ( 1 , $observer -> getChangedUsers ()); 22 } 23 } 1.3.9 Спецификация (Specification) Назначение Строит ясное описание бизнес-правил, на соответствие которым могут быть проверены объекты. Ком- позитный класс спецификация имеет один метод, называемый isSatisfiedBy, который возвращает истину или ложь в зависимости от того, удовлетворяет ли данный объект спецификации. Примеры • RulerZ Диаграмма UML 1.3. Поведенческие шаблоны проектирования (Behavioral) 113 DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы можете найти этот код на GitHub Item.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Behavioral\Specification; 6 7 class Item 8 { 9 public function __construct ( private float $price ) 10 { 11 } 12 13 public function getPrice () : float 14 { 15 return $this -> price ; 16 } 17 } Specification.php 1 2 3 declare (strict_types |