Вы когда-нибудь писали код, а через месяц не могли понять, что там происходит? Или добавляли новую фичу, и всё ломалось? Знакомо. Шаблоны проектирования — это готовые рецепты для типовых задач. Они помогают делать код понятным, гибким и удобным для изменений. В этой статье мы разберём самые популярные шаблоны в PHP на живых примерах.

Что такое шаблоны проектирования и зачем они нужны

Шаблон проектирования (design pattern) — это проверенное решение частой проблемы в программировании. Представьте, что вы строите дом. Вместо того чтобы каждый раз придумывать, как сделать крышу, вы используете готовую схему. Так и тут. Шаблоны делятся на три группы: порождающие (как создавать объекты), структурные (как собирать классы вместе) и поведенческие (как объекты общаются друг с другом).

В PHP шаблоны особенно полезны, потому что язык динамический и часто используется в больших проектах. Они помогают избежать хаоса, когда код разрастается.

Порождающие шаблоны: Singleton, Factory и Builder

Начнём с порождающих. Самый известный — Singleton (одиночка). Он гарантирует, что у класса будет только один экземпляр. Например, подключение к базе данных не должно создаваться каждый раз заново.

class Database {
    private static ?Database $instance = null;
    private PDO $pdo;

    private function __construct() {
        $this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }

    public static function getInstance(): Database {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection(): PDO {
        return $this->pdo;
    }
}

Но будьте осторожны. Singleton часто критикуют за то, что он создаёт глобальное состояние. Используйте его только там, где это действительно нужно, например, для логгера или конфигурации.

Другой полезный шаблон — Factory (фабрика). Он прячет логику создания объектов. Допустим, у вас есть разные типы уведомлений: email, SMS, push. Фабрика решит, какой объект создать.

interface Notification {
    public function send(string $message): void;
}

class EmailNotification implements Notification {
    public function send(string $message): void {
        echo "Email sent: $message";
    }
}

class SMSNotification implements Notification {
    public function send(string $message): void {
        echo "SMS sent: $message";
    }
}

class NotificationFactory {
    public static function create(string $type): Notification {
        return match ($type) {
            'email' => new EmailNotification(),
            'sms' => new SMSNotification(),
            default => throw new InvalidArgumentException("Unknown type")
        };
    }
}

Builder (строитель) пригодится, когда объект сложный и состоит из множества частей. Например, создание отчёта с разными секциями.

Правило: Если конструктор класса принимает больше 3-4 параметров, задумайтесь о Builder. Он сделает код читаемее.

Структурные шаблоны: Adapter и Decorator

Структурные шаблоны помогают организовать классы так, чтобы они работали вместе. Adapter (адаптер) — это как переходник для розетки. Он позволяет использовать несовместимые интерфейсы.

Представьте, у вас есть старая библиотека для работы с XML, а новая система ожидает JSON. Адаптер преобразует одно в другое.

class XmlData {
    public function getXml(): string {
        return 'value';
    }
}

interface JsonDataInterface {
    public function getJson(): string;
}

class XmlToJsonAdapter implements JsonDataInterface {
    private XmlData $xmlData;

    public function __construct(XmlData $xmlData) {
        $this->xmlData = $xmlData;
    }

    public function getJson(): string {
        $xml = simplexml_load_string($this->xmlData->getXml());
        return json_encode($xml);
    }
}

Decorator (декоратор) позволяет динамически добавлять функциональность объекту. Как обёртка для подарка — вы добавляете слои, не меняя сам подарок.

interface Coffee {
    public function getCost(): float;
    public function getDescription(): string;
}

class SimpleCoffee implements Coffee {
    public function getCost(): float {
        return 10;
    }

    public function getDescription(): string {
        return 'Simple coffee';
    }
}

class MilkDecorator implements Coffee {
    private Coffee $coffee;

    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee;
    }

    public function getCost(): float {
        return $this->coffee->getCost() + 2;
    }

    public function getDescription(): string {
        return $this->coffee->getDescription() . ', with milk';
    }
}

Поведенческие шаблоны: Strategy и Observer

Поведенческие шаблоны управляют взаимодействием между объектами. Strategy (стратегия) позволяет менять алгоритм на лету. Например, расчёт стоимости доставки в зависимости от способа.

interface ShippingStrategy {
    public function calculate(float $weight): float;
}

class ExpressShipping implements ShippingStrategy {
    public function calculate(float $weight): float {
        return $weight * 10;
    }
}

class StandardShipping implements ShippingStrategy {
    public function calculate(float $weight): float {
        return $weight * 5;
    }
}

class Order {
    private ShippingStrategy $strategy;

    public function setShippingStrategy(ShippingStrategy $strategy): void {
        $this->strategy = $strategy;
    }

    public function getShippingCost(float $weight): float {
        return $this->strategy->calculate($weight);
    }
}

Observer (наблюдатель) — это когда один объект уведомляет других об изменениях. Как подписка на новости. В PHP есть встроенный SplObserver и SplSubject.

class User implements \SplSubject {
    private \SplObjectStorage $observers;
    private string $email;

    public function __construct() {
        $this->observers = new \SplObjectStorage();
    }

    public function attach(\SplObserver $observer): void {
        $this->observers->attach($observer);
    }

    public function detach(\SplObserver $observer): void {
        $this->observers->detach($observer);
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function setEmail(string $email): void {
        $this->email = $email;
        $this->notify();
    }
}

Сравнение популярных шаблонов

Шаблон Тип Когда использовать Пример
Singleton Порождающий Нужен только один экземпляр Подключение к БД
Factory Порождающий Создание объектов по условию Уведомления
Adapter Структурный Несовместимые интерфейсы XML в JSON
Decorator Структурный Добавление функциональности без наследования Кофе с добавками
Strategy Поведенческий Смена алгоритма на лету Стоимость доставки
Observer Поведенческий Уведомление об изменениях Подписка на события
Лайфхак: Не пытайтесь использовать все шаблоны сразу. Выбирайте только те, которые реально решают вашу проблему. Иначе код станет сложнее, чем без шаблонов.

Заключение

Шаблоны проектирования — это не магия, а инструменты. Они помогают писать код, который легко читать, тестировать и менять. Начните с малого: примените Factory для создания объектов или Decorator для расширения функциональности. Со временем вы научитесь видеть, где какой шаблон подходит. Главное — не переусердствуйте. Иногда простой if работает лучше, чем сложная архитектура.

Помните: хороший код — это код, который понятен другим разработчикам (и вам через полгода). Шаблоны в этом отличные помощники.

Студия WNDER