Привет, добро пожаловать обратно в серию Design Patterns!👻 Это набор статей, которые я решил инициировать, чтобы распространять знания о них👀
Не пропустите другие мои статьи из цикла:
- Паттерн стратегии: https://medium.com/towardsdev/strategy-pattern-for-independent-algorithms-kotlin-70ed24c7bd8b
- Паттерн наблюдателя: https://medium.com/towardsdev/observer-pattern-for-loose-coupling-kotlin-f5ab804609bb
- Паттерн декоратора: https://medium.com/towardsdev/decorator-decorator-pattern-for-object-composition-kotlin-7cec92cbaf7b
- Фабричный шаблонS: https://medium.com/dev-genius/factory-patterns-to-hide-instantiation-kotlin-d5f01cf01921
И не скупитесь на книгу 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
Давайте сразу разберем эти примеры:
global
— плохая идея, поскольку они обычно инициализируются при запуске приложения. Итак, если кто-то ресурсоемкий и в результате приложение никогда его не использует -› пустая трата драгоценных ресурсов👎🏼- Объявить все как
static
может быть вариантом в некоторых случаях, но у него есть существенный недостаток. Который из? Незначительные ошибки, связанные с порядком инициализации, поэтому чрезмерное использованиеstatic
может привести к трудностям в обнаружении проблем.
=>
для нашего спасения был разработан Singleton Pattern, обеспечивающий идентификацию (увы, не всегда, но я упомяну об этом в разделе Дополнительно)
Он использует частные и общедоступные конструкторы с любимым ключевым словом static
.
Позвольте мне показать вам шаги достижения конечного состояния шаблона👨🏻💻:
- Чтобы сделать класс пригодным для инстанцирования, нам нужно
public constructor
:
public ExampleOfClass
2. Чтобы сделать класс невидимым для внешнего мира, мы помещаем его внутрь предыдущего общедоступного класса и делаем так:
private ExampleOfInnerClass
3. Чтобы инициировать этот внутренний класс, нам определенно нужно вызвать какой-то метод. Используйте static
, чтобы сделать его доступным из public
constructor:
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)
они оба создадут свои собственные экземпляры. Отсюда и много хлопот😰
Как мы можем это исправить?
- Используйте ключевое слово
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: оно гарантирует наличие только одного экземпляра класса. Кроме того, он обеспечивает глобальную точку доступа к нему.
На сегодня все. Ознакомьтесь с моими предыдущими статьями из цикла👋
Ты можешь меня найти:
- LinkedIn: www.linkedin.com/in/sleeplesschallenger
- GitHub: https://github.com/SleeplessChallenger
- Литкод: https://leetcode.com/SleeplessChallenger/
- Телеграмма: @SleeplessChallenger