Паттерны проектирования в PHP: примеры и практическое применение

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое паттерны проектирования и зачем они нужны в PHP?

Паттерны проектирования (design patterns) — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. Они не являются готовым кодом, а представляют собой шаблоны, которые можно адаптировать под конкретную ситуацию. Для PHP-разработчика знание паттернов помогает писать более гибкий, поддерживаемый и масштабируемый код.

В этой статье мы разберём четыре базовых паттерна из каталога GoF (Gang of Four), которые чаще всего используются в реальных проектах на PHP. Каждый паттерн будет сопровождаться практическим примером и пояснениями.

1. Одиночка (Singleton)

Паттерн Singleton гарантирует, что у класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему. Это полезно для управления подключениями к базе данных, логгерами или кэшем.

Когда применять?

  • Необходим ровно один объект (например, соединение с БД).
  • Объект используется во всём приложении, и создание дубликатов недопустимо.

Пример реализации:

<?php
class DatabaseConnection
{
    private static ?self $instance = null;
    private PDO $pdo;

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

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

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

    // Запрещаем клонирование и десериализацию
    private function __clone() {}
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize singleton");
    }
}

// Использование:
$db = DatabaseConnection::getInstance();
$pdo = $db->getConnection();
?>

Важно: Singleton часто критикуют за нарушение принципа единственной ответственности и усложнение тестирования. Используйте его осознанно и только там, где это действительно необходимо.

2. Фабричный метод (Factory Method)

Паттерн Factory Method определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. Это позволяет делегировать логику создания объектов дочерним классам.

Когда применять?

  • Заранее неизвестно, какой именно объект потребуется создать.
  • Хочется скрыть сложную логику создания объекта от клиента.

Пример реализации:

<?php
// Интерфейс для всех типов уведомлений
interface Notification
{
    public function send(string $message): void;
}

// Конкретные реализации
class EmailNotification implements Notification
{
    public function send(string $message): void
    {
        echo "Отправка email: {$message}\n";
    }
}

class SMSNotification implements Notification
{
    public function send(string $message): void
    {
        echo "Отправка SMS: {$message}\n";
    }
}

// Абстрактный создатель
abstract class NotificationFactory
{
    abstract public function createNotification(): Notification;

    public function notify(string $message): void
    {
        $notification = $this->createNotification();
        $notification->send($message);
    }
}

// Конкретные фабрики
class EmailFactory extends NotificationFactory
{
    public function createNotification(): Notification
    {
        return new EmailNotification();
    }
}

class SMSFactory extends NotificationFactory
{
    public function createNotification(): Notification
    {
        return new SMSNotification();
    }
}

// Использование:
$factory = new EmailFactory();
$factory->notify("Ваш заказ подтверждён!");

$factory = new SMSFactory();
$factory->notify("Код подтверждения: 1234");
?>

3. Адаптер (Adapter)

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

Когда применять?

  • Нужно использовать стороннюю библиотеку с неудобным интерфейсом.
  • Есть старый код, который нужно интегрировать с новым, не переписывая его.

Пример реализации:

<?php
// Целевой интерфейс, который ожидает клиент
interface PaymentProcessor
{
    public function pay(float $amount): void;
}

// Сторонний класс с несовместимым интерфейсом
class StripeAPI
{
    public function charge(float $amount, string $currency): bool
    {
        echo "Stripe: списание {$amount} {$currency}\n";
        return true;
    }
}

// Адаптер, который приводит StripeAPI к интерфейсу PaymentProcessor
class StripeAdapter implements PaymentProcessor
{
    private StripeAPI $stripe;

    public function __construct(StripeAPI $stripe)
    {
        $this->stripe = $stripe;
    }

    public function pay(float $amount): void
    {
        // Преобразуем вызов
        $this->stripe->charge($amount, 'USD');
    }
}

// Клиентский код
class Order
{
    public function checkout(PaymentProcessor $processor, float $total): void
    {
        $processor->pay($total);
    }
}

// Использование:
$order = new Order();
$stripe = new StripeAPI();
$adapter = new StripeAdapter($stripe);
$order->checkout($adapter, 150.00);
?>

4. Наблюдатель (Observer)

Паттерн Observer определяет зависимость "один ко многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически. В PHP начиная с версии 7.4 есть встроенные интерфейсы SplSubject и SplObserver.

Когда применять?

  • Нужно оповестить несколько объектов об изменении состояния другого объекта.
  • Количество подписчиков может меняться динамически.

Пример реализации с использованием SPL:

<?php
// Субъект (издатель)
class User implements SplSubject
{
    private array $observers = [];
    private string $email;

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

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

    public function detach(SplObserver $observer): void
    {
        $this->observers = array_filter(
            $this->observers,
            fn($o) => $o !== $observer
        );
    }

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

Блоги

Рекомендаций по книгам