Глобальная точка доступа с синглтоном
Представьте, что вы управляете крупной банковской системой. Банку необходима безопасность операций и отслеживаемость всех возможных системных ошибок. В этой системе есть один класс, отвечающий за запись журналов (журналов для исторических целей того, что делается в системе).
При каждой операции, то есть почти всех действиях, выполняемых в системе, ведется журнал с различной информацией. Однако по мере увеличения количества пользователей система начала испытывать проблемы с производительностью. Все пользователи постоянно жаловались на медлительность.
Одна из причин этой проблемы заключалась в том, что каждый раз, когда нам нужно было писать журнал, нам приходилось заново создавать экземпляр класса; с каждым новым экземпляром было занято место в памяти сервера. Поскольку система получила значительное количество одновременных пользователей, она в конечном итоге довела использование физической памяти до предела.
Сделав вывод о том, что такое большое количество экземпляров класса журнала является основной причиной проблемы с производительностью, возникла необходимость уменьшить количество существующих параллельных экземпляров. Если бы вы могли создать экземпляр этого объекта журнала один раз и совместно использовать его во время выполнения системы (период, когда компьютерная программа остается работающей), мы бы решили проблему; вот тут-то и появляется паттерн Синглтон.
Стандарт Singleton
Определение
Как и в случае с проблемой банковской системы, представленной ранее, когда у нас была проблема с производительностью приложения, потому что у нас было большое количество экземпляров класса, создаваемых одновременно с каждым запросом к серверу, нам нужно иметь больший контроль над созданием неконтролируемых экземпляров класса. один класс.
Синглтон предназначен для обеспечения этого контроля, для управления количеством экземпляров, созданных для данного класса, а также для централизации создания этих объектов в единой глобальной точке доступа (к которой можно получить доступ из любой точки системы).
В Интернете есть несколько примеров реализаций Singleton, где основной класс управляет экземпляром других классов. Однако это не гарантирует фактического управления экземплярами класса, поскольку ничего не подозревающий разработчик может игнорировать класс Singleton (который управляет созданием экземпляров) и создавать новые объекты, либо потому, что они не знают об их существовании, либо не хотят используй их.
Верно то, что класс, следующий за этим шаблоном проектирования, управляет созданием своих собственных экземпляров, поэтому в противном случае было бы невозможно создать его экземпляры. Способ ограничения создания новых экземпляров самого класса варьируется от языка к языку. Мы увидим все шаги, чтобы сделать это с помощью PHP 7+.
Синглтон, антипаттерн
Для многих шаблон Singleton считается анти-шаблоном из-за его использования в статической форме (вызов класса без необходимости его создания) и, особенно, благодаря глобальному доступу к экземпляру класса. У использования статических классов много проблем: мы не можем работать с интерфейсами, наш код, помимо прочего, имеет более сильную связь.
У нас также есть проблемы, связанные с глобальным доступом к переменной экземпляра. Это порождает трудное понимание того, почему переменная не связана напрямую с определенной областью системы. У нас также есть риск, что переменная, которую можно изменить в любой точке системы, повлияет на другие части.
Несмотря на все эти недостатки, применение любого стандарта всегда следует оценивать в соответствии с реальной потребностью. В случае синглтона его использование указывается, если требуется одновременный контроль доступа к общему ресурсу или если нам действительно нужен единственный экземпляр.
Диаграмма классов
Ниже приведена диаграмма классов паттернов Singleton в соответствии с нашим примером:
Элементы синглтона
Синглтон - это класс, отвечающий за управление созданием собственных экземпляров и предоставление доступа к этим экземплярам во всем приложении. Это отличается от адаптера, который будет рассмотрен позже, поскольку он состоит из одного элемента.
Решение проблемы логов с единичным экземпляром
В следующем примере мы напишем код, ответственный за решение проблемы нескольких экземпляров банковской системы. Идея состоит в том, чтобы создать класс для записи логов в текстовый файл. При каждом появлении журнала этот журнал записывается в файл.
Этот класс будет следовать шаблону Singleton и позволит создать только один экземпляр независимо от того, сколько раз он используется. В приведенном ниже коде мы начнем создавать класс LogsSingleton, который будет основой для нашего шаблона. Я предлагаю вам создать папку с именем singleton и вставить в нее все файлы для теста.
Первый момент - это создание свойства $ instance (атрибут), в котором будет храниться единственный экземпляр класса. Как видно из примера, для свойства установлено значение static, поскольку сохранение экземпляра в статическом свойстве обеспечивает единственную точку, в которой будет находиться экземпляр класса.
Для свойства установлен уровень защищенной конфиденциальности. Таким образом, мы не можем получить к нему доступ напрямую извне.
Как указано в комментариях, мы будем хранить журналы в текстовом файле с именем logs.txt, а данные будут в формате JSON. Этот формат представляет собой простую структуру для хранения и обмена данными между системами, которая позволяет структурировать информацию и даже определять типы данных.
Нам также понадобится метод, который будет вызываться для записи логов в текстовый файл. Давайте посмотрим, как это будет выглядеть, помня, что метод recordLog () должен находиться внутри класса LogsSingleton (), который вы создали ранее.
Метод recordLog () принимает параметр ($ data) типа массив. Именно в этом параметре мы получим данные для записи в текстовый файл.
В следующем коде мы объявляем переменную $ previousLogs как пустой массив (это позволит избежать ошибок, если файл журнала пуст). Затем, используя метод Size (), мы проверяем, есть ли в текстовом файле что-нибудь, хранящееся внутри; Если размер больше нуля, значит, внутри есть данные (логи).
Мы извлечем контент из собственной функции PHP file_get_contents (), а затем возьмем это содержимое (сохраненное в формате JSON), чтобы преобразовать его в массив с помощью функции json_decode (). В конце концов, переменная $ previousLogs будет содержать пустой массив, если у него нет содержимого в файле, или массив с журналами, ранее записанными в других запусках.
В коде $ previousLogs [] = $ data; мы увеличиваем массив журналов и добавляем новый журнал, даже если он пуст или уже содержит журналы. Мы открываем текстовый файл, чтобы снова ввести текст в строку $ file = fopen ($ filename, ‘w’);.
Затем мы записываем JSON в текстовый файл, fwrite code ($ file, json_encode ($ previous logs)); Функция json_encode () преобразует массив журнала в текст в этом формат. И, наконец, мы вызываем функцию fclose (), отвечающую за закрытие файла, открытого для записи.
Пока что мы создали класс, который может записывать журналы в текстовые файлы и имеет статическую глобальную переменную для хранения своего собственного экземпляра. Однако нам нужно управлять этим экземпляром, поэтому давайте создадим для этого метод:
Как и свойство $ instance, метод receiveInstance () также был объявлен статически. Давайте вызовем его, чтобы запросить новый экземпляр (без создания экземпляра класса журнала), и только этот метод будет иметь внешний доступ к свойству $ instance.
В первой строке внутри него, если, мы проверяем, является ли $ instance пустым, с помощью сравнения empty (self :: $ instance). Если да, это означает, что нет экземпляра класс журнала LogsSingleton () еще не создан; Если это условие истинно, мы создаем экземпляр класса и сохраняем его с кодом self :: $ instance = new self ();. New self () будет таким же, как new LogsSingleton (), но поскольку он находится внутри самого класса, мы можем использовать его таким образом.
В конце концов, с помощью return self :: $ instance; мы всегда возвращаем экземпляр самого класса. При первом вызове он всегда будет проходить внутрь if и создавать экземпляр класса, но в других случаях он будет возвращать только ранее созданный экземпляр.
Теперь я спрашиваю вас, что произойдет, если мы выполним следующий код?
$ InstanceLogs = new LogsSingleton ();
Вопреки ожиданиям, код заработает, и у нас будет новый экземпляр LogsSingleton. Это не соответствует предлагаемым единичным / контролируемым случаям. Чтобы избежать этого неожиданного поведения, мы должны объявить метод конструктора класса с видимостью частного типа:
private function __construct () {}
Таким образом, с новым LogsSingleton () мы могли получить экземпляр класса внутри себя, только вызвав метод receiveInstance (). В дополнение к частному конструктору в PHP есть другие специальные методы, которые позволяют вам получить новый экземпляр класса, и все они должны быть объявлены как частные, как показано ниже:
private function __clone () {}
Таким образом, мы избегаем создания новых экземпляров класса LogsSingleton (). С кодом, разработанным на данный момент, у нас уже есть полная реализация стандарта Singleton.
Магические методы
Магические методы - это функции, доступные в PHP 5. Они были созданы, когда PHP начал развиваться в объектной ориентации. Их можно идентифицировать, начиная с __, и каждый из них имеет определенные функции, такие как __construct, который служит конструктором класса, или __toString (аналогично Java), вызываемый при попытке распечатать объект (преобразовать его в строку).
Чтобы протестировать этот класс, давайте создадим файл index.php и вставим следующий код:
И при первом, и при втором запуске LogsSingleton :: receiveInstance () метод вернет один и тот же экземпляр. Не только значение будет одинаковым, но и обе переменные ($ instance и $ newInstance) будут указывать на один и тот же адрес памяти.
Сравнение $ instance === $ newInstance будет истинным, и мы должны увидеть сообщение «Экземпляры точно такие же!».
Заключение
Помимо представленного примера класса журнала, еще одним очень распространенным случаем являются экземпляры класса подключения к базе данных. Эти классы обычно используются во всей системе.
Также следует отметить, что существуют реализации, в которых класс Singleton допускает более одного экземпляра. Однако это всегда ограниченное и контролируемое количество, которое по-прежнему соответствует шаблону.
Несмотря на все перечисленные отрицательные моменты, которые заставляют разработчиков рассматривать синглтон как антипаттерн, по-прежнему важно знать о его работе и его назначении.
Репозиторий и ссылки
Репозиторий кода Github: https://github.com/augustkohl/design-patterns
ЛУТЦ, М. Программирование на Python: мощное объектно-ориентированное программирование. 2011 г.
ВЕЙСФЕЛЬД М. Процесс объектно-ориентированного мышления. Аддисон-Уэсли Профессионал, 2019.
ГАММА, E .; HELM, R .; ДЖОНСОН Р. Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования. Эддисон-Уэсли Профессионал, 1994.