Привет, добро пожаловать обратно в серию Design Patterns!👻 Это набор статей, которые я решил инициировать, чтобы распространять знания о них👀

Не пропустите другие мои статьи из цикла:

И не скупитесь на книгу O'Reilly Media, если хотите глубже погрузиться в мир паттернов: https://www.oreilly.com/library/view/head-first- дизайн/9781492077992/

Состав:

  • вступление
  • Проблема
  • Принципы дизайна, которым нужно следовать: null в этой статье
  • Дополнительный раздел
  • Окончательный код решения (в этой статье он отличается от предыдущих)
  • Рисунок

Предисловие: как я уже говорил выше, в этой статье я буду использовать не свой собственный код, а код Java, предоставленный авторами книги: https://wickedlysmart.com/head- первые шаблоны дизайна/. Почему так? Вы получите его в основной части статьи✌️

Одноэлементный шаблон

вступление

Когда вы начнете писать программное обеспечение в различных программах, вы обнаружите, что одни и те же шаблоны могут быть реализованы немного по-разному. Пример?

  • Singleton использует static в Java, но в Kotlin это просто object , а в Python используется metaclass и все такое.
  • Adapter (шаблон будет в следующей статье) имеет 2 основные реализации: он использует композицию в Java (из-за отсутствия множественного наследования) и множественное наследование в Python (хотя никто не запрещает вам использовать композицию)

Что я пытаюсь донести до вас, так это то, что концепции одинаковы для разных языков, но реализация может различаться☝️

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

Проблема

Представьте, что у вас есть программа с несколькими потоками, кешем и тому подобным. Мы не хотим, чтобы они смешивались друг с другом.

Возможные решения, которые приходят на ум?

  • использовать globalпеременные
  • использовать static

Давайте сразу разберем эти примеры:

  1. global — плохая идея, поскольку они обычно инициализируются при запуске приложения. Итак, если кто-то ресурсоемкий и в результате приложение никогда его не использует -› пустая трата драгоценных ресурсов👎🏼
  2. Объявить все какstatic может быть вариантом в некоторых случаях, но у него есть существенный недостаток. Который из? Незначительные ошибки, связанные с порядком инициализации, поэтому чрезмерное использование static может привести к трудностям в обнаружении проблем.

=> для нашего спасения был разработан Singleton Pattern, обеспечивающий идентификацию (увы, не всегда, но я упомяну об этом в разделе Дополнительно)

Он использует частные и общедоступные конструкторы с любимым ключевым словом static.

Позвольте мне показать вам шаги достижения конечного состояния шаблона👨🏻‍💻:

  1. Чтобы сделать класс пригодным для инстанцирования, нам нужно public constructor :

public ExampleOfClass

2. Чтобы сделать класс невидимым для внешнего мира, мы помещаем его внутрь предыдущего общедоступного класса и делаем так:

private ExampleOfInnerClass

3. Чтобы инициировать этот внутренний класс, нам определенно нужно вызвать какой-то метод. Используйте static, чтобы сделать его доступным из publicconstructor:

public static ExampleOfInnerClass getInstance()

4. Зачем использовать static для переменной Singleton, например:

private static Singleton uniqueInstance

Прочтите следующий пост в разделе Переполнение стека: https://stackoverflow.com/a/26993425/16543524

В конце концов, давайте посмотрим на окончательный код паттерна Singleton и обсудим возможные его модификации🛠

Дополнительный раздел

Но сначала позволю себе небольшое отступление:

  • Что такое static? Я не большой специалист по JVM, но могу это объяснить: это слово говорит JVM, что она должна инициализировать (или просто помнить) переменные с этим ключевым словом при запуске приложения.
  • Имейте в виду, что Singleton не предназначен для простого шаблона ради шаблона. Внутри него может быть множество методов.
  • Когда другой поток/ресурс получает доступ к Singleton, он проверяет метод: был ли экземпляр уже инициализирован или нет? Посмотрите на метод getInstance() в следующем разделе, если вы не понимаете, о чем я проповедую🧐
  • Этот шаблон нарушает S в SOLID: принцип единой ответственности.

класс должен делать только одно и не связываться с различными

  • Затем Singleton не является слабо связанным.

Эти две последние пули не означают, что паттерн в чем-то уступает. Его часто за это критикуют, но это правда: не всегда мы придерживаемся лучших практик🤷‍♂️

  • В Kotlin нет полноценного шаблона Singleton, то есть это простой object . Вот почему я решил объяснить все на Java
  • Могут возникнуть проблемы с единственным в своем роде материалом, когда у нас есть несколько загрузчиков классов и Singleton. Решение? Укажите загрузчик классов самостоятельно.

Окончательный код решения

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

Как было описано мной в предыдущем чанке:

  • public class Singleton constructor, чтобы сделать класс доступным для внешнего мира
  • private Singleton конструктор, чтобы сделать класс невидимым для внешнего мира
  • public static Singleton getInstance(), чтобы разрешить public class Singleton доступ к этому методу
  • private static Singleton uniqueInstance снова, тот же пост в Stack Overflow: https://stackoverflow.com/a/26993425/16543524

И вы думаете: что можно улучшить? Честно говоря, общая структура остается прежней, но вносятся небольшие изменения (не малые по степени важности, которую они привносят).

Что, если у нас есть несколько потоков, и они одновременно обращаются к Singleton? -› Переполох, так как каждый поток может войти в класс, и после if (uniqueInstance == null) они оба создадут свои собственные экземпляры. Отсюда и много хлопот😰

Как мы можем это исправить?

  1. Используйте ключевое слово synchronized в методе getInstance(). Это заставит поток ждать, пока другой не разблокирует метод.

Соблюдайте код:

Обратите внимание на synchronized

2. «Радостно созданный» синглтон

Сначала посмотрите на код, затем я пошагово объясню, как они это сделали:

Помните static объяснение? Опять же, JVM узнает обо всех static при инициализации приложения. Здесь наша переменная static вызывает метод для создания экземпляра Singleton =>, когда мы будем использовать этот метод дальше, переменная будет уже инициализирована.

3. DCI: блокировка с двойной проверкой

Вы знаете аферу synchronized ? Это пожирает много ресурсов, и если это критично, у нас есть решение:

Таким образом, при таком подходе, если 2 потока оказываются после if (uniqueInstance == null) , код блокирует переменную, а затем вторую проверку (почему? -> подождите пока плз🙏), если все еще null , инициализирует переменную. Оооо, у вас могут возникнуть вопросы❓:

  • зачем второй чек? Итак, 2 потока проходят первую if проверку. Затем мы блокируем synchronized . Первый поток завершил инициализацию, и второй поток начинает работу. А вот во втором if проверяем: instance уже существует -› создавать его не нужно. Мы используем synchronized только один раз, когда экземпляр не реализован.
  • Что такое violate? Он указывает потоку искать текущее значение переменной (в нашем случае это внутри класса), а не кеш потока (где переменная не может быть инициализирована).

‼️Опасность‼️: этот метод не работает до Java 5

4. Мы также можем сделать Singleton с Java Enum, но я пропущу это в статье.

Рисование💫

На картинке вы можете видеть мои каракули🙄 Я нарисовал различные реализации и сделал несколько комментариев.

Если вы что-то не поняли, оставьте комментарий, и я приду, чтобы объяснить

Аутро📣

Академическое определение шаблона Singleton: оно гарантирует наличие только одного экземпляра класса. Кроме того, он обеспечивает глобальную точку доступа к нему.

На сегодня все. Ознакомьтесь с моими предыдущими статьями из цикла👋

Ты можешь меня найти: