fbpx

Blog

Применяем паттерны проектирования в Android. Flyweight. Почему для 127 результат будет true, а для 128 уже false.

Тема паттернов проектирования будет актуальная всегда, потому что писать масштабируемый и читаемый код нужно всегда, независимо под какую платформу вы проектируете. Поэтому в этом посте мы на примере рассмотрим паттерн проектирования Flyweight, он же Легковес или Приспособленец. Итак для чего используется этот паттерн?

Легковес — это структурный паттерн проектирования, который позволяет вместить бóльшее количество объектов в отведённую оперативную память. Легковес экономит память, разделяя общее состояние объектов между собой, вместо хранения одинаковых данных в каждом объекте.

Пример

Рассмотрим мобильное приложение для поиска недвижимости. Скорее всего в такого типа приложении один из экранов для поиска будет состоять из карты и множества маркеров, обозначающих квартиру или дом. Если таких объектов будет очень много, например, больше 100 000 то, учитывая ограниченные ресурсы мобильного телефона, приложение может начать подтормаживать, из-за нехватки оперативной памяти.

Решение

В этом случае нам может помочь

  • Кластеризация
  • Использование паттерна Flyweight. 

Паттерн Легковес предлагает не хранить в классе внешнее состояние, а передавать его в те или иные методы через параметры. Таким образом, одни и те же объекты можно будет повторно использовать в различных контекстах. Но главное — понадобится гораздо меньше объектов, ведь теперь они будут отличаться только внутренним состоянием, а оно имеет не так много вариаций.

А где используется?

Помните, в начале поста был вопрос, что будет в случае сравнения двух чисел 127?

@Test
public void compareIntegersTest() {
    Integer a = 127;
    Integer b = 127;
    assertEquals(true, a == b);
}

В этом случае сравнение вернет результат true и тест будет пройден. Однако в случае сравнения 128 результат будет false и тест провалится.

@Test
public void compareIntegersTest() {
       Integer a = 128;
       Integer b = 128;
       assertEquals(true, a == b);
}

Все дело в том, что используя == мы сравниваем ссылки по которым эти объекты хранятся. Когда мы пишем Integer a = 127 мы создаем Integer (ссылочный тип). Получается что сравнивая a == b мы сравниваем ссылки двух объектов. Но почему-то для 127 результат будет true, а для 128 уже false. Все дело в статичном методе valueOf() который вызывается при инициализации переменной. Этот метод, как раз оптимизирует количество объектов в памяти, используя IntegerCache, и достаёт уже ранее созданные объекты из памяти если они есть. Другими словами, для чисел за пределами интервала от IntegerCache.low до IntegerCache.high будет создан новый объект, ссылка на который будет помещена в переменную типа Integer. Этот диапазон определен по умолчанию от -128 до 127. Поэтому при попытке сравнения двух переменных, ссылающихся на объект 128, вы получаете false, т.к. ссылки разные. Для сравнения объектов по ссылке нужно использовать метод equals(). Как раз это и есть пример использования паттерна Flyweight в реальном мире. Тоже самое касается и StringPool, ShortCache, LongCache, CharacterCache

Шаги реализации

  1. Разделите поля класса, который станет легковесом, на две части:
    • внутреннее состояние: значения этих полей одинаковы для большого числа объектов;
    • внешнее состояние (контекст): значения полей уникальны для каждого объекта.
  2. Оставьте поля внутреннего состояния в классе, но убедитесь, что их значения неизменяемы. Эти поля должны инициализироваться только через конструктор.
  3. Превратите поля внешнего состояния в параметры методов, где эти поля использовались. Затем удалите поля из класса.
  4. Создайте фабрику, которая будет кешировать и повторно отдавать уже созданные объекты. Клиент должен запрашивать из этой фабрики легковеса с определённым внутренним состоянием, а не создавать его напрямую.
  5. Клиент должен хранить или вычислять значения внешнего состояния (контекст) и передавать его в методы объекта легковеса.

Преимущества и недостатки

Плюсы

  • Экономит оперативную память.

Минусы

  • Усложняет код программы из-за введения множества дополнительных классов.
  • Расходует процессорное время на поиск/вычисление контекста.

Ну и что?

Итак, мы выяснили, что паттерн Легковес может использоваться при разработке мобильных приложений под Android OS для экономии памяти путем выделения общего состояния, не выделяя при этом память под каждый одинаковый объект. Как видите в Java этот паттерн тоже активно используется, и теперь вы сможете ответить на вопрос на собеседовании, что будет при сравнении двух чисел 127. Подписывайтесь на телеграм-канал и группу вконтакте для получения полезных материалов и статей.

Пример проекта

Тут сможете посмотреть имплементацию паттерна https://github.com/iluwatar/java-design-patterns/tree/master/flyweight

Если вы хотите прокачать свои знания до middle-уровня, обратите внимание на интенсивный курс по Android-разработке с code-review и консультациями. <<<<👉 Записаться>>>>