The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
Вы, конечно, знаете, что такое ленивая загрузка, верно? И, без сомнения, вы знаете о кешировании. Насколько мне известно, в Java нет элегантного способа реализовать ни одно из них. Вот что я сам узнал с помощью элементов Cactoos.
Предположим, нам нужен объект, который будет шифровать некоторый текст. Говоря более объектно-ориентированным языком, он будет инкапсулировать текст и становиться его зашифрованной формой. Вот как мы будем использовать его (давайте сначала создадим тесты):
Теперь давайте реализуем это очень примитивным способом с использованием одного основного конструктора. Механизм шифрования просто добавит +1
к каждому байту входных данных и предположит, что шифрование не сломает ничего (очень глупое предположение, но для примера оно подойдет).
Пока выглядит правильно? Я протестировал это и оно работает. Если вводом является "Hello, world!"
, то вывод будет "Ifmmp-!xpsme\""
.
Далее, допустим, мы хотим, чтобы наш класс принимал InputStream
и String
. Мы хотим вызвать его, например, так:
Вот наиболее очевидная реализация, с двумя основными конструкторами (опять же, реализация примитивна, но работает):
Технически это работает, но чтение потока происходит прямо внутри конструктора, что является плохой практикой. Основные конструкторы не должны выполнять ничего, кроме присваивания атрибутов, в то время как вторичные могут только создавать новые объекты.
Давайте попробуем провести рефакторинг и внедрить отложенную загрузку:
Работает отлично, но выглядит ужасно. Самая уродливая часть - это, конечно же, эти две строки.
Они делают объект изменяемым и используют NULL. Это некрасиво, поверьте мне. К сожалению, ленивая загрузка и NULL-ссылки всегда идут вместе в классических примерах. Однако есть лучший способ реализации. Давайте переработаем наш класс, на этот раз используя Scalar из Cactoos.
Теперь выглядит намного лучше. Прежде всего, есть только один основной конструктор и два вторичных. Во-вторых, объект является неизменяемым. В-третьих, есть еще много места для улучшений: мы можем добавить больше конструкторов, которые будут принимать другие источники данных, например File
или массив байтов.
Вкратце, атрибут, который должен загружаться “ленивым” способом, представлен внутри объекта как “функция” (лямбда-выражение в Java 8). Пока мы не обращаемся к этому атрибуту, он не загружается. Как только нам потребуется работать с ним, функция выполняется, и у нас есть результат.
Однако у этого кода есть одна проблема. Он будет считывать входной поток каждый раз, когда мы вызываем asString()
, что, очевидно, не будет работать, поскольку только в первый раз поток будет содержать данные. При каждом последующем вызове поток будет просто пустым. Таким образом, нам нужно убедиться, что this.text.value()
выполняет инкапсулированный Scalar
только один раз. Все последующие вызовы должны возвращать ранее вычисленное значение. Поэтому нам нужно кэшировать его. Вот как:
StickyScalar
обеспечивает, что только первый вызов его метода value()
будет передан в инкапсулированный Scalar
. Все остальные вызовы получат результат первого вызова.
Последняя проблема, которую нужно решить, связана с конкурентностью. Код, который у нас есть выше, не является потокобезопасным. Если я создам экземпляр Encrypted5
и передам его двум потокам, которые одновременно вызывают asString()
, результат будет непредсказуемым, просто потому что StickyScalar
не является потокобезопасным. Однако есть еще одно примитивное средство, чтобы нам помочь, это SyncScalar
:
Теперь мы в безопасности, а дизайн элегантен. Он включает ленивую загрузку и кэширование.
Я использую этот подход во многих проектах сейчас, и он кажется удобным, понятным и объектно-ориентированным.
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 05:19