Паттерн Singleton. Шаблоны проектирования.

Было бы странно начать рассматривать шаблоны проектирования и не упомянуть паттерн Singleton, на русском – одиночка. Именно он часто берется в пример при разборе темы паттернов, так как является одним из легких для понимания и реализации. Так называемый “Hellow, World!” в мире шаблонов. Порой его называют антипаттерном. Давайте разберемся в этой статье что он из себя представляет, какие его плюсы и минусы и почему его некоторые программисты недолюбливают. Рекомендую к прочтению статьи по принципам SOLID, для того что бы более детально понимать недостатки и достоинства этого паттерна.

Порождающий паттерн Singleton

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

Если немного поразмышлять в этом направлении – то можно предположить, что паттерн Singleton отвечает за создание одного объекта. В целом, могу сказать, это предположение будет верным. Но есть еще несколько деталей/ограничений.

Паттерн Singleton создает объект и гарантирует что будет создан только один единственный экземпляр класса. Созданный объект будет доступен глобально.

Что же такого интересного в данном паттерне? А хорош он в том, что позволяет создать только один экземпляр класса. При повторной попытке мы будем получать уже созданный ранее объект. Это нам будет гарантировать то, что мы пользуемся одним и тем же объектом каждый раз при обращении к нему. Примером может быть объект подключения к БД, легирования и любой другой объект, который в контексте всей программы будет одним единственным.

Недостатки паттерна Singleton

Чего же его иногда хейтят? Помните, я в начале упомянул про принципы SOLID? Так вот, данный паттерн нарушает один из принципов, а именно принцип единственной ответственности, или, в народе, Single Responsibility. Более детально – статья о SOLID принципах.

Вторым недостатком этого паттерна является отсутствие явной зависимости в других классах. Забегу немного наперед, сказав что объект можно получить/создать с помощью статического метода. Это будет выглядеть примерно следующим образом:

Singleton::getInstance();

Таким образом мы не сможем легко увидеть зависимость класса на синглтоне.

Третьим камнем преткновения будет глобальность данного объекта. Трудно понять кто, где и когда обращался к данному объекту и какое у него вообще состояние на текущий момент.

Еще одним недостатком является сложность тестирования.

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

Реализация паттерна

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

Для начала. подумаем, что нам такого сделать, что бы убрать возможность создания бесконечного числа экземпляров класса? Как нам остановить этот конвейерный процесс? Как запретить пользователю создать новый объект? Правильно, скрыть конструктор. Точнее – сделать его приватным. Таким образом будет невозможно (на самом деле возможно) вызвать конструктор извне класса и создать объект снаружи. Но как же нам тогда его вызвать хотя бы разочек? А точнее – один лишь раз и не более.

Тут на помощь приходит публичный статический метод. Публичность позволит обратиться к нему извне класса. Статичность – возможность вызова метода без создания экземпляра класса. Выше мы его уже увидели мельком. Зачастую его называют getInstance(). Название, которое говорит само за себя. Таким образом мы можем обращаться к этому методу не создавая экземпляр класса. В этом методе создается экземпляр класса и помещается в приватную переменную $instance или возвращает ее значение в случае если оно уже было присвоено ранее. К приватному конструктору будет возможность достучатся в пределах класса. Таким образом в одном методе у нас происходит создание объекта и/или его возврат если он был создан ранее.

Кроме того, что бы добить этот паттерн до конца – нужно объявить несколько магических методов приватными (по крайней мере в PHP). Как вы думаете, какие? Это методы __clone(), __wakeup(). Попробуйте ответить на вопрос почему именно эти методы. Если останется не ясно – предлагаю обсудить в комментариях.

Пример реализации паттерна Singleton

Было бы некрасиво с моей стороны оставить вас без примера напичкав просто информацией о паттерне. Давайте рассмотрим типичный пример реализации паттерна Singleton на PHP.

class Singleton
{
    private static $instance = null;

    /**
     * Запретим вызов конструктора снаружи класса
     */
    private function __construct() { }

    /**
     * Запретим клонирование.
     */
    private function __clone() { }

    /**
     * Запретим десерриализацию объекта
     */
    private function __wakeup() {}

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

        return self::$instance;
    }

    public function doSomething()
    {
        
    }
}

Как мы и обсуждали ранее – мы закрыли доступ сделав приватными методы __construct(), __clone(), __wakeup(). Далее в статическом методе getInstance() происходит магия Singleton-а: мы создаем объект если он не был создан ранее и далее – возвращаем этот объект. Таким образом получаем доступ к экземпляру класса. Вряд ли мы захотим что бы объект просто существовал без надобности. Просто был. Потому добавим публичный метод doSomething(), который уже будет делать что-то очень важное. Который будет настолько необходимым, что даст нам право использовать этот паттерн со всеми его недостатками.

Теперь попробуем использовать наш класс и проверим на столько он Singleton. Для этого сделаем следующее:

$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();

echo ($singleton1 === $singleton2) ? 'Instances are the same' : 'Instances are different';

Если мы все сделали правильно – получим сообщения что экземпляры класса у нас одинаковые. То что мы и хотели добиться.

Таким образом мы рассмотрели один из самых популярных паттернов проектирования, а именно – Singleton. Узнали, что он из себя представляем, как его можно реализовать и какие недостатки он имеет. Рассмотрели пример использования на PHP.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *