Шаблоны проектирование которые отвечают за создание объектов.
Одиночка (Singleton)
Одиночка это самый простой по своей сути паттерн, который отвечает за то, что бы у класса был всего один экземпляр. На моей практике, в чистом понимание это встречалось редко, с использование DI я честно сказать такое вообще не видел в коде. Где это можно применить, ну допустим у нас есть какие-то конфигурации приложения в файле, мы можем сделать класс одиночку, который будет один раз загружать эти конфигурации и все последующие разы мы будем уже доставать из этого экземпляра.
class AppConfig
{
private static ?AppConfig $instance = null;
private array $settings = [];
// 1. Закрываем конструктор, чтобы никто не создал объект через "new"
private function __construct()
{
// Здесь происходит загрузка конфига ...
$this->settings = [
'version' => '2.4.1'
];
}
// 2. Запрещаем клонирование объекта
private function __clone() {}
// 3. Единственная точка доступа к объекту
public static function getInstance(): AppConfig
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// Бизнес-логика: получение настройки
public function get(string $key): mixed
{
return $this->settings[$key] ?? null;
}
}
Тогда в любом участке кода мы можем получать настройки вот так:
$version = AppConfig::getInstance()->get('version');
Данный паттерн еще считают антипаттерном, в связи с тем что он вызывает проблемы с тестированием, нарушает один из принципов единственной ответственности (т.к. по факту выполняет две задачи, свою логику и контроль за своими экземплярами), возможных проблем с мутируемостью объекта, подробно рассматривать эти проблемы не имеет смысла, т.к. сейчас таким паттерном в чистом виде не пользуется.
Фабричный метод (Factory Method)
Это единый интерфейс для создания объектов, через специальный фабричный метод, реализация уже это фабричного метода будет зависеть от конкретного класса фабрики. Рассмотрим пример, когда нам нужно провести оплату заказа в нашем интернет магазине. У нас есть различные способы оплаты, от клиента мы получаем способ который он выбрал и сумму заказа.
// Интерфейс для получения ссылки на оплату
interface PaymentGatewayInterface
{
/**
* @param float $amount Сумма к оплате
* @return string URL-адрес, куда перенаправить пользователя для оплаты
*/
public function getUrl(float $amount): string;
}
// Интеграция с Sber (банковские карты)
class SberGateway implements PaymentGatewayInterface
{
public function __construct(private string $shopId, private string $secretKey) {}
public function getUrl(float $amount): string
{
// Здесь сложная логика: подпись запроса, curl-запрос к API Sber
return "https://sber.ru" . $amount;
}
}
// Интеграция с T-банком (СБП)
class TinkoffGateway implements PaymentGatewayInterface
{
public function __construct(private string $apiToken) {}
public function getUrl(float $amount): string
{
// Здесь сложная логика: подпись запроса, curl-запрос к API Tinkoff
return "https://tinkoff.ru";
}
}
// Просто интерфейс фабрики с фабричным методом
interface PaymentGatewayFactoryInterface
{
public function create(): PaymentGatewayInterface;
}
// Реализация фабричного метода для Sber
class SberGatewayFactory implements PaymentGatewayFactoryInterface
{
public function __construct(private string $token) {}
public function create(): PaymentGatewayInterface
{
return new SberGateway($this->token);
}
}
// Реализация фабричного метода для Tinkoff
class TinkoffGatewayFactory implements PaymentGatewayFactoryInterface
{
public function __construct(private string $token) {}
public function create(): PaymentGatewayInterface
{
return new TinkoffGateway($this->token);
}
}
Тогда один из вариантов использования будет такой:
class PaymentGateway
{
public function __construct(private PaymentGatewayFactoryInterface $factory) {}
public function payment(float $amount): void
{
$gateway = $this->factory->create();
$url = $gateway->getUrl($amount);
// редирект на страницу оплаты
}
}
$sberFactory = SberGatewayFactory('секретный ключ');
$paymentGateway = new PaymentGateway($sberFactory);
$paymentGateway->payment(100.00);
Абстрактная фабрика (Abstract Factory)
Абстрактная фабрика в отличии от фабричного метода создает не один объект, а целую группу объектов условно связанных между собой. Допустим, что для разных способов оплаты у нас будут разные способы уведомления, для Sber мы используем почту, для Tinkoff Sms, тогда нам удобнее сделать фабрику, которая будет создавать несколько объектов для проведения оплаты различными способами.
// Интерфейс для нотификации
interface PaymentNotificationInterface
{
public function sendSuccessMessage(): void;
}
// Почта
class EmailNotification implements PaymentNotificationInterface
{
public function sendSuccessMessage(): void
{
// Отправка письма на почту клиенту
}
}
// СМС
class SmsNotification implements PaymentNotificationInterface
{
public function sendSuccessMessage(): void
{
// Отправка письма на почту клиенту
}
}
// Интерфейс для фабрики
interface PaymentSberFactoryInterface
{
public function createGateway(): PaymentGatewayInterface;
public function createNotification(): PaymentNotificationInterface;
}
// Интерфейс для Sber фабрики
class PaymentSberFactory implements PaymentSberFactoryInterface
{
public function createGateway(): PaymentGatewayInterface
{
$sberFactory = SberGatewayFactory('секретный ключ');
return $sberFactory->create();
}
public function createNotification(): PaymentNotificationInterface
{
return new EmailNotification();
}
}
// Интерфейс для Tinkoff фабрики
class PaymentTinkoffFactory implements PaymentSberFactoryInterface
{
public function createGateway(): PaymentGatewayInterface
{
$tinkoffFactory = TinkoffGatewayFactory('секретный ключ');
return $tinkoffFactory->create();
}
public function createNotification(): PaymentNotificationInterface
{
return new SmsNotification();
}
}
Тогда использование будет выглядеть так:
$factory = match ($paymentMethod) {
'sber' => new PaymentSberFactory(),
'tinkoff' => new PaymentTinkoffFactory(),
default => throw new \InvalidArgumentException("Метод оплаты не поддерживается")
};
$gateway = $factory->createGateway();
$notification = $factory->createNotification();
// Проводим оплату
$paymentUrl = $gateway->getUrl($amount);
// Отправляем уведомление
$notification->sendSuccessMessage();
Строитель (Builder)
Этот паттерн позволяет создавать сложные объекты пошагово. Часто бывает, что есть какой-то объект имеющий множество различных параметров, часть из которых не обязательна, часть зависит друг от друга, данный паттерн решает эту проблему. Приведу пример, допустим у нас есть объект заказ которые мы передаем в внешний АПИ, у заказ есть куча параметр, начиная от позиций, сроков доставки, стоимости доставки, стоимости заказа, оплаты и т.д. Мы мождем сделать один класс, которые будет создавать нам этот заказ пошагово.
class OrderBuilder
{
private Order $order;
public function createNewOrder(int $orderId): self
{
$this->order = new Order($orderId);
return $this;
}
// Шаг 1: Добавление позиций
public function addProduct(string $name, float $price, int $quantity = 1): self
{
$this->order->addItem([
'name' => $name,
'price' => $price,
'quantity' => $quantity
]);
return $this;
}
// Шаг 2: Расчет и добавление доставки
public function setShipping(string $address, string $deliveryType, float $cost): self
{
$this->order->setDelivery($address, $cost);
return $this;
}
// Финал: Получение готового и проверенного заказа
public function getOrder(): Order
{
return $this->order;
}
}
Использование:
$builder = new OrderBuilder();
// Инициализируем сборку нового заказа
$order = $builder->createNewOrder(88412)
// Шаг А: Добавляем товары
->addProduct('Смартфон', 45000.00, 1)
->addProduct('Чехол для смартфона', 1200.00, 2)
// Шаг Б: Добавляем доставку
->setShipping('г. Москва, ул. Ленина, д. 10', 'courier', 300.00)
->getOrder();
Прототип (Prototype)
Паттерн, основывается на создании путем клонирования уже существующего объекта. Парой нам проще клонировать объект и поменять его незначительную часть, чем заново все отстраивать. Простой пример, у нас есть конфигурация для доставки заказа, это вес, габариты, и т.д., нам необходимо посчитать срок доставки для нескольких конфигураций, и что бы для каждого расчете не создавать новую конфигурацию со всеми параметрами, мы просто копируем и меням только способ доставки
class DeliveryConfiguration
{
public function __construct(
public string $address,
public float $cost,
public float $weight,
public string $deliveryType
) {}
}
$cdekConfigure = new DeliveryConfiguration(
address: 'Адрес',
cost: 500.00,
weight: 1450.00,
deliveryType: 'cdek',
);
$boxberryConfigure = clone $cdekConfigure;
$boxberryConfigure->deliveryType = 'boxberry';
Заключение
Это была основная пятерка пораждающих паттернов, каждый из которых решает определенную задачу, в примерах показано изолированное их использование, на практике же это чаще комбинированные случаи, паттерны могут пересекаться между собой и это нормально, главное помнить, что паттерны это не жесткие законы, а гибкие конструкторы.
0 Сообщений