Структурные паттерны это шаблоны которые определяют структуру компонентов и их взаимосвязь.

Адаптер

Это шаблон который позволяет связать между собой два объекта с разными интерфейсами. В моей практике очень часто встречался данный паттерн, приведу реальный пример, есть интернет магазин, у него куча интеграций с разными API служб доставки, у каждой службы доставки есть свои нюансы, СДК и т.д., для того что бы с ними было удобно работать в коде, мы в логике используем один общий интерфейс для них, но вот не задача, интерфейсы у всех доставок разные и вот что бы решить эту проблему делают адаптер.

<?php

// Единый интерфейс доставки, который непосредственно учавствует в бизнес логике
interface DeliveryServiceInterface {
    public function calculateCost(int $weightInGrams, string $zipCode): float;
    public function createShipment(string $address, array $items): string;
}

// Сторонняя библиотека СДЭК (CdekApiClient)
class CdekSdk {
    public function getTariff(string $postalCode, float $weightInKg): float {
        // Логика запроса к API СДЭК...
        return 450.00; 
    }

    public function registerOrder(array $deliveryData): array {
        // Логика создания заказа в СДЭК
        // Ожидает структуру: ['delivery_address' => '...', 'cargo' => [...]]
        return ['cdek_id' => 'CDEK-998877'];
    }
}

// Адаптер, который реализует общий интерфейс
class CdekAdapter implements DeliveryServiceInterface {
    private CdekSdk $cdekApi;

    public function __construct(CdekSdk $cdekApi) {
        $this->cdekApi = $cdekApi;
    }

    // Адаптируем расчет стоимости
    public function calculateCost(int $weightInGrams, string $zipCode): float {
        // Конвертируем граммы в килограммы для СДЭК
        $weightInKg = $weightInGrams / 1000;
        
        // Вызываем метод из SDK СДЭК
        return $this->cdekApi->getTariff($zipCode, $weightInKg);
    }

    // Адаптируем создание отправления
    public function createShipment(string $address, array $items): string {
        // Пересобираем массив под формат, который требует СДЭК
        $cdekFormatData = [
            'delivery_address' => $address,
            'cargo' => $items
        ];

        $response = $this->cdekApi->registerOrder($cdekFormatData);

        // Возвращаем только трек-номер в едином строковом формате нашего сайта
        return $response['cdek_id'];
    }
}

Таким образом, мы адаптируем стороннюю библиотеку, для работы с нашей бизнес логикой.

Мост

Этот паттерн немного схож с Адаптером, но он не просто делает адаптирование одного интерфейса к другом, а как бы проектирует структуру для возможных расширения функционала, путем деления его на абстракцию и реализацию. Самый простой пример который у меня был на практике, это различные способы отправки СМС, по мере жизни интернет магазина, менялись провайдеры для отправки СМС клиентам, при проектирование мы сразу заложили, что бизнес в любой момент может сменить на другого провайдера который будет дешевле или более подходящим.

<?php

// Интерфейс реализации
interface MessageGateway {
    public function connect(): void;
    public function transmit(string $to, string $body): bool;
}

// Провайдер 1: Сервис StreamTelekom
class StreamTelekomGateway implements MessageGateway {
    public function connect(): void { /* Логика авторизации в API StreamTelekom */ }
    public function transmit(string $to, string $body): bool {
        echo "[StreamTelekom API] Отправлено на {$to}: \"{$body}\"\n";
        return true;
    }
}

// Провайдер 2: Сервис Beeline
class BeelineGateway implements MessageGateway {
    public function connect(): void { /* Логика авторизации в API Beeline */ }
    public function transmit(string $to, string $body): bool {
        echo "[Beeline API] Отправлено на {$to}: \"{$body}\"\n";
        return true;
    }
}

Тогда абстракция будет выглядеть так:

<?php

// Абстракция
abstract class Notification {
    // Тот самый Мост — ссылка на интерфейс реализации
    protected MessageGateway $gateway;

    public function __construct(MessageGateway $gateway) {
        $this->gateway = $gateway;
    }

    abstract public function send(string $recipient, string $message): void;
}

// Расширенная абстракция: Обычное текстовое уведомление
class PlainNotification extends Notification {
    public function send(string $recipient, string $message): void {
        $this->gateway->connect();
        // Отправляем текст как есть
        $this->gateway->transmit($recipient, $message);
    }
}

Теперь при появлении новых сервисов по отправке мы можем просто добавлять новый провайдер, не меняя старый код.

Компоновщик

Это паттерн который позволяет объединять объекты в древовидные структуры и работать с ними так, будто это один единственный объект. Наглядный пример это реализация дерева категорий товаров в интернет магазине. У нас товары, есть категории, категория может содержать как товары так и подкатегории.

<?php

// Интерфейс для элемента каталога
interface CatalogComponent {
    public function getProductsCount(): int;
}

// Товар
class Product implements CatalogComponent {
    private string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    // Товар всегда считает себя за единицу
    public function getProductsCount(): int {
        return 1;
    }
}

// Категория
class Category implements CatalogComponent {
    private string $name;
    private array $children = []; // Здесь могут быть и Product, и Category

    public function __construct(string $name) {
        $this->name = $name;
    }

    // Метод для добавления элементов в коробку/категорию
    public function add(CatalogComponent $component): void {
        $this->children[] = $component;
    }

    // Самая важная часть: контейнер делегирует работу своим детям
    public function getProductsCount(): int {
        $total = 0;
        foreach ($this->children as $child) {
            // Рекурсивно вызываем тот же метод у детей
            $total += $child->getProductsCount();
        }
        return $total;
    }
}

Точно так же можно привести пример с заказом и позициями в заказе, допустим когда хотим посчитать вес\стоимость заказа.

Декоратор

Паттерн, который позволяет динамически добавлять объектам новые обязанности на лету. При этом вы не меняете исходный код самого объекта и не используете громоздкое наследование. На практике, частенько происходит что используя какой-то класс, нам не хватает какой-то мелочи в его реализации, не обязательно даже мелочи, писать новый класс выглядит нагружено, да и не во всех случаях возможно нам это нужно, тогда можно прибегнуть к декорирования или обертыванию класса и последующем его дополнением. Допустим у нас была отправка сообщения на почту, в какой-то момент нам потребовалось логировать часть отправляемых сообщений, а часть оставить по прежнему.

<?php

// Интерфейс для отправки сообщения
interface Notifier {
    public function send(string $message): void;
}

// Класс который отправляет сообщение на почту
class EmailNotifier implements Notifier {
    public function send(string $message): void {
        echo "📧 Отправлено Email-письмо с текстом: \"{$message}\"\n";
    }
}

Для того, что бы добавить логирование, мы пишем декоратор

<?php

// Абстракция для декоратора
abstract class NotifierDecorator implements Notifier {
    protected Notifier $wrappedNotifier;

    public function __construct(Notifier $notifier) {
        $this->wrappedNotifier = $notifier;
    }

    // По умолчанию просто перенаправляет вызов обернутому объекту
    public function send(string $message): void {
        $this->wrappedNotifier->send($message);
    }
}

// Декоратор: добавляет логирование
class LoggingDecorator extends NotifierDecorator {
    public function send(string $message): void {
        // Свое действие ДО отправки
        echo "📝 [LOG] Попытка отправки сообщения в " . date('H:i:s') . "\n";
        
        parent::send($message);
    }
}

Фасад

Паттерн, который предоставляет один простой интерфейс для работы с сложной системой классов или методов. Представим, что в нашем интернет магазине при создании заказа, нужно забронировать товар на складе, посчитать бонусы, выставить счет, отправить клиенту уведомления, без фасада контроллеру пришлось бы работать с кучей классов.

<?php

class InventorySystem {
    public function isAvailable(string $productId): bool { return true; }
    public function reserve(string $productId): void { echo "📦 Товар зарезервирован на складе.\n"; }
}

class PaymentGateway {
    public function charge(float $amount): bool {
        echo "💳 Списано {$amount} руб. через эквайринг.\n";
        return true;
    }
}

class InvoiceGenerator {
    public function createReceipt(): void { echo "🧾 Чек сформирован и отправлен в налоговую.\n"; }
}

class SmsNotifier {
    public function sendNotification(): void { echo "📱 SMS: 'Ваш заказ успешно оформлен!'\n"; }
}

Делаем фасад, который прячет всю эту рутину внутри себя. Он знает, в каком порядке вызывать методы подсистемы.

<?php

class OrderFacade {
    private InventorySystem $inventory;
    private PaymentGateway $payment;
    private InvoiceGenerator $invoice;
    private SmsNotifier $sms;

    // Внедряем все сложные зависимости в конструктор
    public function __construct(
        InventorySystem $inventory,
        PaymentGateway $payment,
        InvoiceGenerator $invoice,
        SmsNotifier $sms
    ) {
        $this->inventory = $inventory;
        $this->payment = $payment;
        $this->invoice = $invoice;
        $this->sms = $sms;
    }

    // Единственный простой метод для внешнего мира
    public function placeOrder(string $productId, float $price): bool {
        echo "=== НАЧАЛО ОФОРМЛЕНИЯ ЗАКАЗА ===\n";

        if (!$this->inventory->isAvailable($productId)) {
            echo "❌ Товара нет на складе.\n";
            return false;
        }

        $this->inventory->reserve($productId);
        $this->payment->charge($price);
        $this->invoice->createReceipt();
        $this->sms->sendNotification();

        echo "=== ЗАКАЗ УСПЕШНО ЗАВЕРШЕН ===\n";
        return true;
    }
}

Приспособленец

Это паттерн который позволяет экономить память на общих данных. Допустим у нас есть класс товара, который содержит просто куча общей информации с целой группой товаров, зачем нам занимать память всеми этими данными для тысячи товаров, когда можно их вынести в отдельный класс приспособленец.

<?php

// Приспособленец, хранящий общие свойства
class ProductMetadata {
    private string $brand;
    private string $category;
    private array $warrantyDetails; // Тяжелый массив с правилами возврата и гарантии

    public function __construct(string $brand, string $category, array $warrantyDetails) {
        $this->brand = $brand;
        $this->category = $category;
        $this->warrantyDetails = $warrantyDetails;
    }

    // Метод принимает внешнее (уникальное) состояние товара извне в момент рендера
    public function renderCard(string $sku, float $price, int $stock): void {
        echo "🛒 Товар [SKU: {$sku}] | Бренд: {$this->brand} | Категория: {$this->category} | ";
        echo "Цена: {$price} руб. | Остаток: {$stock} шт.\n";
    }
}

// Продукт
class ProductContext {
    private string $sku;
    private float $price;
    private int $stock;
    
    // Ссылка на приспособленца
    private ProductMetadata $metadata;

    public function __construct(string $sku, float $price, int $stock, ProductMetadata $metadata) {
        $this->sku = $sku;
        $this->price = $price;
        $this->stock = $stock;
        $this->metadata = $metadata;
    }

    public function display(): void {
        // Передаем уникальное состояние внутрь приспособленца для вывода
        $this->metadata->renderCard($this->sku, $this->price, $this->stock);
    }
}

Заместитель

Заместитель очень похож по своей реализации на декоратор, но несет в себе другую цель. Декоратор используется для расширения функционала, а заместитель, как бы перехватывает вызовы. Самый частый и простой пример, это кеширование запросов в какой-нибудь сервис, API. Что бы каждый раз не грузить данные которые редко меняются, мы можем сделать обертку, в которой проверять наличие закешированного результат и отдавать сразу готовый результат.

<?php

// Интерфейс для генерации отчета
interface ReportGenerator {
    public function generateMonthlyReport(string $month): string;
}

// Класс реальной генерации отчета
class RealReportGenerator implements ReportGenerator {
    public function generateMonthlyReport(string $month): string {
        // Эмуляция тяжелого запроса к внешнему API / БД
        echo "⏳ [API] Запрашиваем терабайты данных за {$month}... (это заняло 5 секунд)\n";
        sleep(5); 
        
        return "📊 Финансовый отчет за {$month}: Выручка +150%, Расходы -20%";
    }
}

class ReportCache implements ReportGenerator {
    private RealReportGenerator $realService;
    private array $cache = [];
    private string $userRole;

    public function __construct(RealReportGenerator $realService, string $userRole) {
        $this->realService = $realService;
        $this->userRole = $userRole; // Передаем роль текущего пользователя
    }

    public function generateMonthlyReport(string $month): string {
        // 1. Проверяющий прокси (Access Control)
        if ($this->userRole !== 'ADMIN' && $this->userRole !== 'CEO') {
            throw new Exception("🚫 Ошибка доступа: У вас нет прав для просмотра финансовых отчетов!");
        }

        // 2. Кэширующий прокси (Caching)
        if (isset($this->cache[$month])) {
            echo "⚡ [PROXY] Возвращаем отчет за {$month} из КЭША мгновенно!\n";
            return $this->cache[$month];
        }

        // 3. Если в кэше нет и права есть — делегируем работу реальному сервису
        echo "🔍 [PROXY] В кэше ничего нет. Обращаемся к реальному сервису...\n";
        $report = $this->realService->generateMonthlyReport($month);
        
        // Сохраняем в кэш на будущее
        $this->cache[$month] = $report;

        return $report;
    }
}