Патерны программирования. DesignPatternsphp documentation
Скачать 1.98 Mb.
|
' ; 12 } 13 } TextElement.php 44 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Composite; 6 7 class TextElement implements Renderable 8 { 9 public function __construct ( private string $text ) 10 { 11 } 12 13 public function render () : string 14 { 15 return $this -> text ; 16 } 17 } Тест Tests/CompositeTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Composite\Tests; 6 7 use DesignPatterns\Structural\Composite\Form; 8 use DesignPatterns\Structural\Composite\TextElement; 9 use DesignPatterns\Structural\Composite\InputElement; 10 use PHPUnit\Framework\TestCase; 11 12 class CompositeTest extends TestCase 13 { 14 public function testRender () 15 { 16 $form = new Form(); 17 $form -> addElement ( new TextElement( 'Email:' )); 18 $form -> addElement ( new InputElement()); 19 $embed = new Form(); 20 $embed -> addElement ( new TextElement( 'Password:' )); 21 $embed -> addElement ( new InputElement()); 22 $form -> addElement ( $embed ); 23 24 // This is just an example, in a real world scenario it is important to remember ␣ ˓→ that web browsers do not 25 // currently support nested forms 26 27 $this -> assertSame ( 28 ' ˓→ ' , (continues on next page) 1.2. Структурные шаблоны проектирования (Structural) 45 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 29 $form -> render () 30 ); 31 } 32 } 1.2.4 Преобразователь Данных (Data Mapper) Назначение Преобразователь Данных — это паттерн, который выступает в роли посредника для двунаправлен- ной передачи данных между постоянным хранилищем данных (часто, реляционной базы данных) и представления данных в памяти (слой домена, то что уже загружено и используется для логической обработки). Цель паттерна в том, чтобы держать представление данных в памяти и постоянное хра- нилище данных независимыми друг от друга и от самого преобразователя данных. Слой состоит из одного или более mapper-а (или объектов доступа к данным), отвечающих за передачу данных. Реа- лизации mapper-ов различаются по назначению. Общие mapper-ы могут обрабатывать всевозоможные типы сущностей доменов, а выделенные mapper-ы будет обрабатывать один или несколько конкретных типов. Ключевым моментом этого паттерна, в отличие от Активной Записи (Active Records) является то, что модель данных следует Принципу Единой Обязанности SOLID. Примеры • DB Object Relational Mapper (ORM) : Doctrine2 использует DAO под названием «EntityRepository» 46 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Диаграмма UML Код Вы можете найти этот код на GitHub User.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DataMapper; 6 7 class User 8 { 9 public static function fromState ( array $state ) : User 10 { (continues on next page) 1.2. Структурные шаблоны проектирования (Structural) 47 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 11 // validate state before accessing keys! 12 13 return new self( 14 $state [ 'username' ], 15 $state [ 'email' ] 16 ); 17 } 18 19 public function __construct ( private string $username , private string ) 20 { 21 } 22 23 public function getUsername () : string 24 { 25 return $this -> username ; 26 } 27 28 public function getEmail () : string 29 { 30 return $this -> ; 31 } 32 } UserMapper.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DataMapper; 6 7 use InvalidArgumentException; 8 9 class UserMapper 10 { 11 public function __construct ( private StorageAdapter $adapter ) 12 { 13 } 14 15 /** 16 * finds a user from storage based on ID and returns a User object located 17 * in memory. Normally this kind of logic will be implemented using the Repository ␣ ˓→ pattern. 18 * However the important part is in mapRowToUser() below, that will create a ␣ ˓→ business object from the 19 * data fetched from storage 20 */ 21 public function findById (int $id ) : User 22 { 23 $result = $this -> adapter -> find ( $id ); 24 25 if ( $result === null ) { (continues on next page) 48 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 26 throw new InvalidArgumentException( "User # $id not found" ); 27 } 28 29 return $this -> mapRowToUser ( $result ); 30 } 31 32 private function mapRowToUser ( array $row ) : User 33 { 34 return User :: fromState ( $row ); 35 } 36 } StorageAdapter.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DataMapper; 6 7 class StorageAdapter 8 { 9 public function __construct ( private array $data ) 10 { 11 } 12 13 /** 14 * @return array|null 15 */ 16 public function find (int $id ) 17 { 18 if ( isset ( $this -> data [ $id ])) { 19 return $this -> data [ $id ]; 20 } 21 22 return null ; 23 } 24 } Тест Tests/DataMapperTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DataMapper\Tests; 6 7 use InvalidArgumentException; 8 use DesignPatterns\Structural\DataMapper\StorageAdapter; (continues on next page) 1.2. Структурные шаблоны проектирования (Structural) 49 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 9 use DesignPatterns\Structural\DataMapper\User; 10 use DesignPatterns\Structural\DataMapper\UserMapper; 11 use PHPUnit\Framework\TestCase; 12 13 class DataMapperTest extends TestCase 14 { 15 public function testCanMapUserFromStorage () 16 { 17 $storage = new StorageAdapter([ 1 => [ 'username' => 'domnikl' , 'email' => ˓→ 'liebler.dominik@gmail.com' ]]); 18 $mapper = new UserMapper( $storage ); 19 20 $user = $mapper -> findById ( 1 ); 21 22 $this -> assertInstanceOf (User :: class , $user ); 23 } 24 25 public function testWillNotMapInvalidData () 26 { 27 $this -> expectException (InvalidArgumentException :: class ); 28 29 $storage = new StorageAdapter([]); 30 $mapper = new UserMapper( $storage ); 31 32 $mapper -> findById ( 1 ); 33 } 34 } 1.2.5 Декоратор (Decorator) Назначение Динамически добавляет новую функциональность в экземпляры классов. Примеры • Web Service Layer: Декораторы JSON и XML для REST сервисов (в этом случае, конечно, только один из них может быть разрешен). 50 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Диаграмма UML Код Вы можете найти этот код на GitHub Booking.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator; 6 7 interface Booking 8 { 9 public function calculatePrice () : int; 10 11 public function getDescription () : string; 12 } BookingDecorator.php 1.2. Структурные шаблоны проектирования (Structural) 51 DesignPatternsPHP Documentation, Выпуск 1.0 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator; 6 7 abstract class BookingDecorator implements Booking 8 { 9 public function __construct ( protected Booking $booking ) 10 { 11 } 12 } DoubleRoomBooking.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator; 6 7 class DoubleRoomBooking implements Booking 8 { 9 public function calculatePrice () : int 10 { 11 return 40 ; 12 } 13 14 public function getDescription () : string 15 { 16 return 'double room' ; 17 } 18 } ExtraBed.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator; 6 7 class ExtraBed extends BookingDecorator 8 { 9 private const PRICE = 30 ; 10 11 public function calculatePrice () : int 12 { 13 return $this -> booking -> calculatePrice () + self :: PRICE ; 14 } 15 16 public function getDescription () : string 17 { (continues on next page) 52 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 18 return $this -> booking -> getDescription () ' with extra bed' ; 19 } 20 } WiFi.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator; 6 7 class WiFi extends BookingDecorator 8 { 9 private const PRICE = 2 ; 10 11 public function calculatePrice () : int 12 { 13 return $this -> booking -> calculatePrice () + self :: PRICE ; 14 } 15 16 public function getDescription () : string 17 { 18 return $this -> booking -> getDescription () ' with wifi' ; 19 } 20 } Тест Tests/DecoratorTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\Decorator\Tests; 6 7 use DesignPatterns\Structural\Decorator\DoubleRoomBooking; 8 use DesignPatterns\Structural\Decorator\ExtraBed; 9 use DesignPatterns\Structural\Decorator\WiFi; 10 use PHPUnit\Framework\TestCase; 11 12 class DecoratorTest extends TestCase 13 { 14 public function testCanCalculatePriceForBasicDoubleRoomBooking () 15 { 16 $booking = new DoubleRoomBooking(); 17 18 $this -> assertSame ( 40 , $booking -> calculatePrice ()); 19 $this -> assertSame ( 'double room' , $booking -> getDescription ()); 20 } (continues on next page) 1.2. Структурные шаблоны проектирования (Structural) 53 DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 21 22 public function testCanCalculatePriceForDoubleRoomBookingWithWiFi () 23 { 24 $booking = new DoubleRoomBooking(); 25 $booking = new WiFi( $booking ); 26 27 $this -> assertSame ( 42 , $booking -> calculatePrice ()); 28 $this -> assertSame ( 'double room with wifi' , $booking -> getDescription ()); 29 } 30 31 public function testCanCalculatePriceForDoubleRoomBookingWithWiFiAndExtraBed () 32 { 33 $booking = new DoubleRoomBooking(); 34 $booking = new WiFi( $booking ); 35 $booking = new ExtraBed( $booking ); 36 37 $this -> assertSame ( 72 , $booking -> calculatePrice ()); 38 $this -> assertSame ( 'double room with wifi with extra bed' , $booking -> ˓→ getDescription ()); 39 } 40 } 1.2.6 Внедрение Зависимости (Dependency Injection) Назначение Для реализации слабосвязанной архитектуры. Чтобы получить более тестируемый, сопровождаемый и расширяемый код. Использование Объект DatabaseConfiguration внедряется в DatabaseConnection и последний получает всё, что ему необходимо из переменной $ config. Без DI, конфигурация будет создана непосредственно в Connection, что не очень хорошо для тестирования и расширения Connection, так как связывает эти классы напрямую. Примеры • The Doctrine2 ORM использует Внедрение Зависимости например для конфигурации, которая внедряется в объект Connection. Для целей тестирования, можно легко создать макет объекта конфигурации и внедрить его в объект Connection, подменив оригинальный. • Во многих фреймворках уже имеются контейнеры для DI, которые создают объекты через массив с конфигурацией и внедряют туда, где это нужно (например в Контроллеры). 54 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 Диаграмма UML 1.2. Структурные шаблоны проектирования (Structural) 55 DesignPatternsPHP Documentation, Выпуск 1.0 Код Вы можете найти этот код на GitHub DatabaseConfiguration.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DependencyInjection; 6 7 class DatabaseConfiguration 8 { 9 public function __construct ( 10 private string $host , 11 private int $port , 12 private string $username , 13 private string $password 14 ) { 15 } 16 17 public function getHost () : string 18 { 19 return $this -> host ; 20 } 21 22 public function getPort () : int 23 { 24 return $this -> port ; 25 } 26 27 public function getUsername () : string 28 { 29 return $this -> username ; 30 } 31 32 public function getPassword () : string 33 { 34 return $this -> password ; 35 } 36 } DatabaseConnection.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DependencyInjection; 6 7 class DatabaseConnection 8 { 9 public function __construct ( private DatabaseConfiguration $configuration ) (continues on next page) 56 Глава 1. Паттерны DesignPatternsPHP Documentation, Выпуск 1.0 (продолжение с предыдущей страницы) 10 { 11 } 12 13 public function getDsn () : string 14 { 15 // this is just for the sake of demonstration, not a real DSN 16 // notice that only the injected config is used here, so there is 17 // a real separation of concerns here 18 19 return sprintf ( 20 '%s:%s@%s:%d' , 21 $this -> configuration -> getUsername (), 22 $this -> configuration -> getPassword (), 23 $this -> configuration -> getHost (), 24 $this -> configuration -> getPort () 25 ); 26 } 27 } Тест Tests/DependencyInjectionTest.php 1 2 3 declare (strict_types = 1 ); 4 5 namespace DesignPatterns\Structural\DependencyInjection\Tests; 6 7 use DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration; 8 use DesignPatterns\Structural\DependencyInjection\DatabaseConnection; 9 use PHPUnit\Framework\TestCase; 10 11 class DependencyInjectionTest extends TestCase 12 { 13 public function testDependencyInjection () 14 { 15 $config = new DatabaseConfiguration( 'localhost' , 3306 , 'domnikl' , '1234' ); 16 $connection = new DatabaseConnection( $config ); 17 18 $this -> assertSame ( 'domnikl:1234@localhost:3306' , $connection -> getDsn ()); 19 } 20 } 1.2. Структурные шаблоны проектирования (Structural) 57 DesignPatternsPHP Documentation, Выпуск 1.0 1.2.7 Фасад (Facade) Назначение The primary goal of a Facade Pattern is not to avoid you having to read the manual of a complex API. It’s only a side-effect. The first goal is to reduce coupling and follow the Law of Demeter. Фасад предназначен для разделения клиента и подсистемы путем внедрения многих (но иногда только одного) интерфейсов, и, конечно, уменьшения общей сложности. • Фасад не запрещает прямой доступ к подсистеме. Просто он делает его проще и понятнее. • Вы можете (и вам стоило бы) иметь несколько фасадов для одной подсистемы. Вот почему хороший фасад не содержит созданий экземпляров классов (new) внутри. Если внутри фасада создаются объекты для реализации каждого метода, это не Фасад, это Строитель или [Аб- страктная|Статическая|Простая] Фабрика [или Фабричный Метод]. Лучший фасад не содержит new или конструктора с type-hinted параметрами. Если вам необходимо создавать новые экземпляры классов, в таком случае лучше использовать Фабрику в качестве аргу- мента. 58 Глава 1. Паттерны |