Как устроен паттерн Singleton?

«Как устроен паттерн Singleton?» — вопрос из категории Паттерны, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Классическая реализация на PHP включает:

  1. Приватный конструктор — чтобы предотвратить создание экземпляра оператором new извне класса.
  2. Приватное статическое поле — для хранения единственного экземпляра.
  3. Публичный статический метод (обычно getInstance()) — контролирует доступ к экземпляру: создает его при первом вызове и возвращает существующий при последующих.
  4. Запрет клонирования и десериализации — для защиты от создания дубликатов.

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

class DatabaseConnection
{
    private static ?self $instance = null;
    private PDO $connection;

    // 1. Приватный конструктор
    private function __construct()
    {
        $this->connection = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }

    // 2. Статический метод для получения экземпляра
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 3. Метод для получения реального соединения
    public function getConnection(): PDO
    {
        return $this->connection;
    }

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

// Использование
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();

var_dump($db1 === $db2); // true - это один и тот же объект

Важные замечания:

  • Потокобезопасность: Базовая реализация не является потокобезопасной. В многопоточных средах (например, Swoole) требуется синхронизация.
  • Антипаттерн: Singleton часто критикуют как антипаттерн, так как он вводит глобальное состояние, что усложняет модульное тестирование (затрудняет изоляцию зависимостей) и нарушает принцип единственной ответственности.
  • Альтернативы: Во многих случаях лучше использовать Dependency Injection (DI), передавая единственный экземпляр в качестве зависимости через контейнер (например, в Symfony или Laravel), что сохраняет контроль над жизненным циклом, но делает зависимости явными и тестируемыми.