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

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

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

Начать курс

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

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

В этой статье мы разберём три основных паттерна: Singleton, Factory и Observer. Для каждого приведём реальные примеры на PHP и объясним, в каких ситуациях их стоит применять.

Почему стоит изучать паттерны в PHP?

  • Упрощение кода — единообразный подход к решению задач.
  • Повторное использование — паттерны легко адаптировать под разные проекты.
  • Лёгкость поддержки — код становится понятным для других разработчиков.
  • Тестируемость — использование паттернов упрощает написание unit-тестов.

1. Паттерн Singleton (Одиночка)

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

Пример: Класс для работы с БД

<?php
class Database {
    private static ?Database $instance = null;
    private PDO $connection;

    private function __construct() {
        // Подключение к БД (пример с SQLite)
        $this->connection = new PDO('sqlite:example.db');
        $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

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

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

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

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

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

  • Когда нужен ровно один экземпляр класса (например, подключение к БД).
  • Для глобального доступа к ресурсу (логгер, кэш).
  • В ситуациях, где создание нескольких экземпляров приведёт к конфликтам.

Минусы: нарушает принцип единственной ответственности и усложняет тестирование (создаёт глобальное состояние).

2. Паттерн Factory (Фабрика)

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";
    }
}

// Фабрика
class NotificationFactory {
    public static function create(string $type): Notification {
        return match ($type) {
            'email' => new EmailNotification(),
            'sms' => new SMSNotification(),
            default => throw new \InvalidArgumentException("Неизвестный тип: {$type}"),
        };
    }
}

// Использование
$notification = NotificationFactory::create('email');
$notification->send('Привет, мир!');
?>

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

  • Когда создание объекта требует сложной логики или зависимости от внешних условий.
  • Когда нужно скрыть от клиента конкретные классы реализаций.
  • Для уменьшения связанности кода (принцип Dependency Inversion).

3. Паттерн Observer (Наблюдатель)

Observer определяет зависимость "один ко многим" между объектами: когда один объект (субъект) меняет своё состояние, все зависимые объекты (наблюдатели) автоматически уведомляются и обновляются.

Пример: Система подписки на новости

<?php
// Интерфейс наблюдателя
interface Observer {
    public function update(string $event): void;
}

// Субъект (издатель)
class NewsPublisher {
    private array $observers = [];

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

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

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

    public function publishNews(string $title): void {
        echo "Опубликована новость: {$title}\n";
        $this->notify($title);
    }
}

// Конкретные наблюдатели
class EmailSubscriber implements Observer {
    public function update(string $event): void {
        echo "Email-подписчик получил: {$event}\n";
    }
}

class TelegramBot implements Observer {
    public function update(string $event): void {
        echo "Telegram-бот отправил: {$event}\n";
    }
}

// Использование
$publisher = new NewsPublisher();
$publisher->attach(new EmailSubscriber());
$publisher->attach(new TelegramBot());

$publisher->publishNews("Новая версия PHP 8.3");
?>

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

  • Когда изменения в одном объекте требуют обновления других объектов.
  • Для реализации событийно-ориентированной архитектуры.
  • В системах с подписками, уведомлениями или логированием.

Сравнение паттернов

Паттерн Назначение Когда использовать
Singleton Гарантирует единственный экземпляр

Блоги

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