Представь, что ты строишь дом. Можно каждый раз класть кирпичи по-новому, придумывая свои способы. А можно использовать проверенные решения: фундамент, стены, крыша. В программировании то же самое. Паттерны проектирования — это готовые чертежи для типовых задач. Они экономят время, нервы и делают код понятным для других.

В этой статье мы разберём три самых востребованных паттерна в PHP: Singleton, Factory и Observer. Никакой магии, только практика. Поехали.

Singleton: когда объект должен быть один

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

В PHP Singleton реализуется так:

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

    // Приватный конструктор — нельзя создать через new
    private function __construct() {
        $this->connection = 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->connection;
    }
}

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

Обрати внимание: конструктор приватный. Снаружи нельзя написать new Database(). Только через getInstance(). Это и есть суть.

Лайфхак: Singleton часто критикуют за скрытые зависимости. Используй его только для по-настоящему глобальных ресурсов (логи, БД, кэш). В современных фреймворках (Laravel, Symfony) вместо Singleton применяют сервис-контейнеры — они гибче.

Factory: делегируем создание объектов

Factory (фабрика) — это паттерн, который выносит логику создания объектов в отдельный класс. Представь, что ты пишешь интернет-магазин. Есть разные типы товаров: книги, электроника, одежда. Каждый тип требует своей логики при создании. Фабрика берёт это на себя.

Пример простой фабрики:

<?php
interface Product {
    public function getName(): string;
}

class Book implements Product {
    public function getName(): string {
        return 'Книга';
    }
}

class Electronics implements Product {
    public function getName(): string {
        return 'Электроника';
    }
}

class ProductFactory {
    public static function create(string $type): Product {
        return match($type) {
            'book' => new Book(),
            'electronics' => new Electronics(),
            default => throw new InvalidArgumentException('Неизвестный тип товара'),
        };
    }
}

// Использование
$product = ProductFactory::create('book');
echo $product->getName(); // Выведет: Книга
?>

Зачем это нужно? Если завтра добавится новый тип товара (например, «Мебель»), ты меняешь только фабрику, а не весь код, где создаются объекты. Меньше правок — меньше багов.

ПаттернКогда использовать
Simple FactoryКогда создание объекта простое, но хочешь изолировать логику
Factory MethodКогда подклассы должны сами решать, какой объект создавать
Abstract FactoryКогда нужно создавать семейства связанных объектов (например, для разных тем оформления)
Правило: Если в коде встречаешь много new в разных местах — это повод задуматься о фабрике. Она централизует создание и упрощает тестирование.

Observer: уведомляем подписчиков

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

Реализация в PHP:

<?php
interface Observer {
    public function update(string $event): void;
}

class User {
    private array $observers = [];

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

    public function register(): void {
        echo 'Пользователь зарегистрирован.\n';
        $this->notify('user.registered');
    }

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

class EmailNotifier implements Observer {
    public function update(string $event): void {
        if ($event === 'user.registered') {
            echo 'Отправляем приветственное письмо.\n';
        }
    }
}

class Logger implements Observer {
    public function update(string $event): void {
        echo 'Записываем в лог: ' . $event . '\n';
    }
}

// Использование
$user = new User();
$user->attach(new EmailNotifier());
$user->attach(new Logger());
$user->register();
?>

Вывод:

Пользователь зарегистрирован.
Отправляем приветственное письмо.
Записываем в лог: user.registered.

Класс User не знает, кто его слушает. Можно добавлять новых наблюдателей без изменения самого пользователя. Это принцип открытости/закрытости в действии.

Советы по выбору паттерна

Не пытайся использовать паттерны везде. Они — инструмент, а не цель. Вот простые критерии:

  • Singleton — если ресурс действительно глобальный и единственный (БД, логгер).
  • Factory — если создание объекта сложное или зависит от условий.
  • Observer — если нужно, чтобы одно событие вызывало несколько реакций, и список реакций может меняться.

Помни: плохой код с паттернами остаётся плохим кодом. Сначала пиши просто, потом рефактори.

Лайфхак: В PHP есть встроенные интерфейсы для Observer — SplSubject и SplObserver. Они уже содержат базовую логику. Используй их, чтобы не писать велосипед.

Резюме

Паттерны проектирования — это не магия, а проверенные временем решения. Singleton помогает управлять единственным экземпляром. Factory избавляет от размазанного по коду создания объектов. Observer делает систему событий гибкой.

Главное — не злоупотребляй. Паттерн должен решать проблему, а не создавать новую. Начни с малого: посмотри на свой код, найди места, где ты часто пишешь new или где одно изменение тянет за собой правки в десяти файлах. Примени паттерн — и почувствуешь разницу.

Студия WNDER