
2024-05-10
Порождающие шаблоны проектирования (Creational)
2023-10-24
Порождающие шаблоны проектирования ( Creational ) - это паттерны, которые имеют дело с механизмом создания объекта и пытаются создать объекты в порядке, подходящем к ситуации. Обычная форма создания объекта может привести к проблемам проектирования или увеличивать сложность конструкции. Порождающие шаблоны проектирования решают эту проблему, определённым образом контролируя процесс создания объекта.
Абстрактная фабрика призвана создавать серии связанных или зависимых объектов без указания их конкретных классов. Обычно все созданные классы реализуют один и тот же интерфейс. Клиент абстрактной фабрики не заботится о том, как создаются эти объекты, он просто знает, как они сочетаются друг с другом.
WriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface WriterFactory
{
public function createCsvWriter(): CsvWriter;
public function createJsonWriter(): JsonWriter;
}
CsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface CsvWriter
{
public function write(array $line): string;
}
JsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface JsonWriter
{
public function write(array $data, bool $formatted): string;
}
UnixCsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixCsvWriter implements CsvWriter
{
public function write(array $line): string
{
return join(',', $line) . "\n";
}
}
UnixJsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixJsonWriter implements JsonWriter
{
public function write(array $data, bool $formatted): string
{
$options = 0;
if ($formatted) {
$options = JSON_PRETTY_PRINT;
}
return json_encode($data, $options);
}
}
UnixWriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixWriterFactory implements WriterFactory
{
public function createCsvWriter(): CsvWriter
{
return new UnixCsvWriter();
}
public function createJsonWriter(): JsonWriter
{
return new UnixJsonWriter();
}
}
WinCsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinCsvWriter implements CsvWriter
{
public function write(array $line): string
{
return join(',', $line) . "\r\n";
}
}
WinJsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinJsonWriter implements JsonWriter
{
public function write(array $data, bool $formatted): string
{
$options = 0;
if ($formatted) {
$options = JSON_PRETTY_PRINT;
}
return json_encode($data, $options);
}
}
WinWriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinWriterFactory implements WriterFactory
{
public function createCsvWriter(): CsvWriter
{
return new WinCsvWriter();
}
public function createJsonWriter(): JsonWriter
{
return new WinJsonWriter();
}
}
Tests/AbstractFactoryTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\AbstractFactory\Tests;
use DesignPatterns\Creational\AbstractFactory\CsvWriter;
use DesignPatterns\Creational\AbstractFactory\JsonWriter;
use DesignPatterns\Creational\AbstractFactory\UnixWriterFactory;
use DesignPatterns\Creational\AbstractFactory\WinWriterFactory;
use DesignPatterns\Creational\AbstractFactory\WriterFactory;
use PHPUnit\Framework\TestCase;
class AbstractFactoryTest extends TestCase
{
public function provideFactory()
{
return [
[new UnixWriterFactory()],
[new WinWriterFactory()]
];
}
/**
* @dataProvider provideFactory
*/
public function testCanCreateCsvWriterOnUnix(WriterFactory $writerFactory)
{
$this->assertInstanceOf(JsonWriter::class, $writerFactory->createJsonWriter());
$this->assertInstanceOf(CsvWriter::class, $writerFactory->createCsvWriter());
}
}
Строитель — это интерфейс для производства частей сложного объекта. Иногда, если Строитель лучше знает о том, что он строит, этот интерфейс может быть абстрактным классом с методами по-умолчанию (адаптер). Если у вас есть сложное дерево наследования для объектов, логично иметь сложное дерево наследования и для их строителей.
Director.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
/**
* Director is part of the builder pattern. It knows the interface of the builder
* and builds a complex object with the help of the builder
*
* You can also inject many builders instead of one to build more complex objects
*/
class Director
{
public function build(Builder $builder): Vehicle
{
$builder->createVehicle();
$builder->addDoors();
$builder->addEngine();
$builder->addWheel();
return $builder->getVehicle();
}
}
Builder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
interface Builder
{
public function createVehicle(): void;
public function addWheel(): void;
public function addEngine(): void;
public function addDoors(): void;
public function getVehicle(): Vehicle;
}
TruckBuilder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Door;
use DesignPatterns\Creational\Builder\Parts\Engine;
use DesignPatterns\Creational\Builder\Parts\Wheel;
use DesignPatterns\Creational\Builder\Parts\Truck;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
class TruckBuilder implements Builder
{
private Truck $truck;
public function addDoors(): void
{
$this->truck->setPart('rightDoor', new Door());
$this->truck->setPart('leftDoor', new Door());
}
public function addEngine(): void
{
$this->truck->setPart('truckEngine', new Engine());
}
public function addWheel(): void
{
$this->truck->setPart('wheel1', new Wheel());
$this->truck->setPart('wheel2', new Wheel());
$this->truck->setPart('wheel3', new Wheel());
$this->truck->setPart('wheel4', new Wheel());
$this->truck->setPart('wheel5', new Wheel());
$this->truck->setPart('wheel6', new Wheel());
}
public function createVehicle(): void
{
$this->truck = new Truck();
}
public function getVehicle(): Vehicle
{
return $this->truck;
}
}
CarBuilder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Door;
use DesignPatterns\Creational\Builder\Parts\Engine;
use DesignPatterns\Creational\Builder\Parts\Wheel;
use DesignPatterns\Creational\Builder\Parts\Car;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
class CarBuilder implements Builder
{
private Car $car;
public function addDoors(): void
{
$this->car->setPart('rightDoor', new Door());
$this->car->setPart('leftDoor', new Door());
$this->car->setPart('trunkLid', new Door());
}
public function addEngine(): void
{
$this->car->setPart('engine', new Engine());
}
public function addWheel(): void
{
$this->car->setPart('wheelLF', new Wheel());
$this->car->setPart('wheelRF', new Wheel());
$this->car->setPart('wheelLR', new Wheel());
$this->car->setPart('wheelRR', new Wheel());
}
public function createVehicle(): void
{
$this->car = new Car();
}
public function getVehicle(): Vehicle
{
return $this->car;
}
}
Parts/Vehicle.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
abstract class Vehicle
{
final public function setPart(string $key, object $value)
{
}
}
Parts/Truck.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Truck extends Vehicle
{
}
Parts/Car.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Car extends Vehicle
{
}
Parts/Engine.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Engine
{
}
Parts/Wheel.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Wheel
{
}
Parts/Door.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Door
{
}
Tests/DirectorTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Tests;
use DesignPatterns\Creational\Builder\Parts\Car;
use DesignPatterns\Creational\Builder\Parts\Truck;
use DesignPatterns\Creational\Builder\TruckBuilder;
use DesignPatterns\Creational\Builder\CarBuilder;
use DesignPatterns\Creational\Builder\Director;
use PHPUnit\Framework\TestCase;
class DirectorTest extends TestCase
{
public function testCanBuildTruck()
{
$truckBuilder = new TruckBuilder();
$newVehicle = (new Director())->build($truckBuilder);
$this->assertInstanceOf(Truck::class, $newVehicle);
}
public function testCanBuildCar()
{
$carBuilder = new CarBuilder();
$newVehicle = (new Director())->build($carBuilder);
$this->assertInstanceOf(Car::class, $newVehicle);
}
}
Выгодное отличие от SimpleFactory в том, что вы можете вынести реализацию создания объектов в подклассы. В простых случаях, этот абстрактный класс может быть только интерфейсом. Этот паттерн является «настоящим» Шаблоном Проектирования, потому что он следует «Принципу инверсии зависимостей» также известному как «D» в S.O.L.I.D. Это означает, что класс FactoryMethod зависит от абстракций, а не от конкретных классов. Это существенный плюс в сравнении с SimpleFactory или StaticFactory.
Logger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
interface Logger
{
public function log(string $message);
}
StdoutLogger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLogger implements Logger
{
public function log(string $message)
{
echo $message;
}
}
FileLogger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class FileLogger implements Logger
{
public function __construct(private string $filePath)
{
}
public function log(string $message)
{
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
LoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
interface LoggerFactory
{
public function createLogger(): Logger;
}
StdoutLoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLoggerFactory implements LoggerFactory
{
public function createLogger(): Logger
{
return new StdoutLogger();
}
}
FileLoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class FileLoggerFactory implements LoggerFactory
{
public function __construct(private string $filePath)
{
}
public function createLogger(): Logger
{
return new FileLogger($this->filePath);
}
}
Tests/FactoryMethodTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod\Tests;
use DesignPatterns\Creational\FactoryMethod\FileLogger;
use DesignPatterns\Creational\FactoryMethod\FileLoggerFactory;
use DesignPatterns\Creational\FactoryMethod\StdoutLogger;
use DesignPatterns\Creational\FactoryMethod\StdoutLoggerFactory;
use PHPUnit\Framework\TestCase;
class FactoryMethodTest extends TestCase
{
public function testCanCreateStdoutLogging()
{
$loggerFactory = new StdoutLoggerFactory();
$logger = $loggerFactory->createLogger();
$this->assertInstanceOf(StdoutLogger::class, $logger);
}
public function testCanCreateFileLogging()
{
$loggerFactory = new FileLoggerFactory(sys_get_temp_dir());
$logger = $loggerFactory->createLogger();
$this->assertInstanceOf(FileLogger::class, $logger);
}
}
Порождающий паттерн, который предоставляет набор заранее инициализированных объектов, готовых к использованию («пул»), что не требует каждый раз создавать и уничтожать их.
Хранение объектов в пуле может заметно повысить производительность в ситуациях, когда стоимость и скорость инициализации экземпляра класса высоки, а количество одновременно используемых экземпляров в любой момент времени является низким. Время на получение объекта из пула легко прогнозируется, тогда как создание нового объекта (особенно с сетевым оверхедом) может занимать неопределенное время.
Однако эти преимущества в основном относятся к объектам, которые изначально являются дорогостоящими по времени создания. Например, соединения с базой данных, соединения сокетов, потоков или инициализация больших графических объектов, таких как шрифты или растровые изображения. В некоторых ситуациях, использование простого пула объектов (которые не зависят от внешних ресурсов, а только занимают память) может оказаться неэффективным и приведёт к снижению производительности.
WorkerPool.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Pool;
use Countable;
class WorkerPool implements Countable
{
/**
* @var StringReverseWorker[]
*/
private array $occupiedWorkers = [];
/**
* @var StringReverseWorker[]
*/
private array $freeWorkers = [];
public function get(): StringReverseWorker
{
if (count($this->freeWorkers) === 0) {
$worker = new StringReverseWorker();
} else {
$worker = array_pop($this->freeWorkers);
}
$this->occupiedWorkers[spl_object_hash($worker)] = $worker;
return $worker;
}
public function dispose(StringReverseWorker $worker): void
{
$key = spl_object_hash($worker);
if (isset($this->occupiedWorkers[$key])) {
unset($this->occupiedWorkers[$key]);
$this->freeWorkers[$key] = $worker;
}
}
public function count(): int
{
return count($this->occupiedWorkers) + count($this->freeWorkers);
}
}
StringReverseWorker.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Pool;
class StringReverseWorker
{
public function run(string $text): string
{
return strrev($text);
}
}
Tests/PoolTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Pool\Tests;
use DesignPatterns\Creational\Pool\WorkerPool;
use PHPUnit\Framework\TestCase;
class PoolTest extends TestCase
{
public function testCanGetNewInstancesWithGet()
{
$pool = new WorkerPool();
$worker1 = $pool->get();
$worker2 = $pool->get();
$this->assertCount(2, $pool);
$this->assertNotSame($worker1, $worker2);
}
public function testCanGetSameInstanceTwiceWhenDisposingItFirst()
{
$pool = new WorkerPool();
$worker1 = $pool->get();
$pool->dispose($worker1);
$worker2 = $pool->get();
$this->assertCount(1, $pool);
$this->assertSame($worker1, $worker2);
}
}
Помогает избежать затрат на создание объектов стандартным способом (new Foo()), а вместо этого создаёт прототип и затем клонирует его. Большие объемы данных (например, создать 1000000 строк в базе данных сразу через ORM).
BookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
abstract class BookPrototype
{
protected string $title;
protected string $category;
abstract public function __clone();
final public function getTitle(): string
{
return $this->title;
}
final public function setTitle(string $title): void
{
$this->title = $title;
}
}
BarBookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
class BarBookPrototype extends BookPrototype
{
protected string $category = 'Bar';
public function __clone()
{
}
}
FooBookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
class FooBookPrototype extends BookPrototype
{
protected string $category = 'Foo';
public function __clone()
{
}
}
Tests/PrototypeTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype\Tests;
use DesignPatterns\Creational\Prototype\BarBookPrototype;
use DesignPatterns\Creational\Prototype\FooBookPrototype;
use PHPUnit\Framework\TestCase;
class PrototypeTest extends TestCase
{
public function testCanGetFooBook()
{
$fooPrototype = new FooBookPrototype();
$barPrototype = new BarBookPrototype();
for ($i = 0; $i < 10; $i++) {
$book = clone $fooPrototype;
$book->setTitle('Foo Book No ' . $i);
$this->assertInstanceOf(FooBookPrototype::class, $book);
}
for ($i = 0; $i < 5; $i++) {
$book = clone $barPrototype;
$book->setTitle('Bar Book No ' . $i);
$this->assertInstanceOf(BarBookPrototype::class, $book);
}
}
}
Она отличается от Статической Фабрики тем, что собственно не является статической. Таким образом, вы можете иметь множество фабрик с разными параметрами. Простая фабрика всегда должна быть предпочтительнее Статической фабрики!
SimpleFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\SimpleFactory;
class SimpleFactory
{
public function createBicycle(): Bicycle
{
return new Bicycle();
}
}
Bicycle.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\SimpleFactory;
class Bicycle
{
public function driveTo(string $destination)
{
}
}
Использование
$factory = new SimpleFactory();
$bicycle = $factory->createBicycle();
$bicycle->driveTo('Paris');
Tests/SimpleFactoryTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\SimpleFactory\Tests;
use DesignPatterns\Creational\SimpleFactory\Bicycle;
use DesignPatterns\Creational\SimpleFactory\SimpleFactory;
use PHPUnit\Framework\TestCase;
class SimpleFactoryTest extends TestCase
{
public function testCanCreateBicycle()
{
$bicycle = (new SimpleFactory())->createBicycle();
$this->assertInstanceOf(Bicycle::class, $bicycle);
}
}
Это считается анти-паттерном! Для лучшей тестируемости и сопровождения кода используйте Инъекцию Зависимости (Dependency Injection)!
Позволяет содержать только один экземпляр объекта в приложении, которое будет обрабатывать все обращения, запрещая создавать новый экземпляр.
DB Connector для подключения к базе данных
Logger
Config Manager
Threads Handling
Блокировка файла в приложении (есть только один в файловой системе с одновременным доступом к нему)
Singleton.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Singleton;
use Exception;
final class Singleton
{
private static ?Singleton $instance = null;
/**
* gets the instance via lazy initialization (created on first usage)
*/
public static function getInstance(): Singleton
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* is not allowed to call from outside to prevent from creating multiple instances,
* to use the singleton, you have to obtain the instance from Singleton::getInstance() instead
*/
private function __construct()
{
}
/**
* prevent the instance from being cloned (which would create a second instance of it)
*/
private function __clone()
{
}
/**
* prevent from being unserialized (which would create a second instance of it)
*/
public function __wakeup()
{
throw new Exception("Cannot unserialize singleton");
}
}
Tests/SingletonTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Singleton\Tests;
use DesignPatterns\Creational\Singleton\Singleton;
use PHPUnit\Framework\TestCase;
class SingletonTest extends TestCase
{
public function testUniqueness()
{
$firstCall = Singleton::getInstance();
$secondCall = Singleton::getInstance();
$this->assertInstanceOf(Singleton::class, $firstCall);
$this->assertSame($firstCall, $secondCall);
}
}
Подобно AbstractFactory, этот паттерн используется для создания ряда связанных или зависимых объектов. Разница между этим шаблоном и Абстрактной Фабрикой заключается в том, что Статическая Фабрика использует только один статический метод, чтобы создать все допустимые типы объектов. Этот метод, обычно, называется factory или build.
StaticFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
use InvalidArgumentException;
/**
* Note1: Remember, static means global state which is evil because it can't be mocked for tests
* Note2: Cannot be subclassed or mock-upped or have multiple different instances.
*/
final class StaticFactory
{
public static function factory(string $type): Formatter
{
return match ($type) {
'number' => new FormatNumber(),
'string' => new FormatString(),
default => throw new InvalidArgumentException('Unknown format given'),
};
}
}
Formatter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
interface Formatter
{
public function format(string $input): string;
}
FormatString.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
class FormatString implements Formatter
{
public function format(string $input): string
{
return $input;
}
}
FormatNumber.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
class FormatNumber implements Formatter
{
public function format(string $input): string
{
return number_format((int) $input);
}
}
Tests/StaticFactoryTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory\Tests;
use InvalidArgumentException;
use DesignPatterns\Creational\StaticFactory\FormatNumber;
use DesignPatterns\Creational\StaticFactory\FormatString;
use DesignPatterns\Creational\StaticFactory\StaticFactory;
use PHPUnit\Framework\TestCase;
class StaticFactoryTest extends TestCase
{
public function testCanCreateNumberFormatter()
{
$this->assertInstanceOf(FormatNumber::class, StaticFactory::factory('number'));
}
public function testCanCreateStringFormatter()
{
$this->assertInstanceOf(FormatString::class, StaticFactory::factory('string'));
}
public function testException()
{
$this->expectException(InvalidArgumentException::class);
StaticFactory::factory('object');
}
}
Структурные шаблоны проектирования (Structural)
Ура! Я наконец-то дописал статью как собирать собственные бандлы на Symfony 6!!!
Статья про EasyAdmin всё ещё в процессе )))
Не, ну мне же надо на чем-то тестировать твиттер локальный...
Я тут еще много полезного буду выкладывать, так что заходите обязательно почитать.
Сайтик пока что в разработке - это далеко не окончательная версия - по сути это то что удалось слепить за 8 часов.
Комментарии