Android interaction design pattern

Применяем паттерны проектирования в Android. Flyweight

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

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

Пример

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

Решение

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

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

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

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

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

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

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

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

  1. Разделите поля класса, который станет легковесом, на две части:
  • внутреннее состояние: значения этих полей одинаковы для большого числа объектов;
  • внешнее состояние (контекст): значения полей уникальны для каждого объекта.

2. Оставьте поля внутреннего состояния в классе, но убедитесь, что их значения неизменяемы. Эти поля должны инициализироваться только через конструктор.

3. Превратите поля внешнего состояния в параметры методов, где эти поля использовались. Затем удалите поля из класса.

4. Создайте фабрику, которая будет кешировать и повторно отдавать уже созданные объекты. Клиент должен запрашивать из этой фабрики легковеса с определённым внутренним состоянием, а не создавать его напрямую.

5. Клиент должен хранить или вычислять значения внешнего состояния (контекст) и передавать его в методы объекта легковеса.

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

Плюсы

Минусы

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

Ну и что?

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

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

Понравилась статья? Не забудь подписаться и поставить лайк статье, а ещё

Читайте также:  Метал слуг для андроид

Источник

Шаблоны проектирования мобильных приложений. Command Processor

“Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте.”
Мартин Голдинг

При разработке одного из проектов, использующего GooglePlacesAPI, у меня возникла проблема организации сетевого взаимодействия между моим Android–приложением и API сервера. Интуиция и “лапша” из AsyncTask’ов подсказывала, что должны быть другие способы организации такого рода взаимодействия. Так я наткнулся на шаблон проектирования CommandProcessor. Об использовании этого паттерна проектирования в Android-приложениях я и хочу рассказать.

Для начала, опишу задачу, которую нужно было решить. Требовалось написать приложение, использующее Google Places API, показывающее превью любого места на карте, которое выбрал пользователь, а далее, если пользователь захочет получить больше информации (например просмотреть больше картинок), то подгружать картинки по заданному Id выбранного места, и показывать уже все картинки, относящиеся к выбранному месту. Самым очевидным на тот момент для меня способом было использование AsyncTask. Но после некоторых попыток стало ясно, что должны быть и другие способы, более удобные. Использование AsyncTask’ ов было неудобным потому что:

1) Чтобы получить превью какого-нибудь места, необходимо было сначала сделать запрос для получения информации о всех местах, которые находились рядом с выбранным пользователем местом.

2) По полученным Id сформировать и отправить запрос о получении фотографии-превью.

3) При клике на превью получить все картинки относящиеся к этому месту.

Таким образом, при использовании AsyncTask’ов получался некий «водопад» и пришлось бы использовать один AsyncTask внутри другого. И тогда, погуглив, я нашел информацию о паттерне Command Processor, который отлично справляется с задачами, описанными выше.

Паттерн проектирования CommandProcessor разделяет запросы к сервису от их выполнения. Главный компонент паттерна — CommandProcessor, управляет запросами, планирует их выполнение, а также предоставляет дополнительный сервис, например, хранение запросов для позднего выполнения или отмены запроса. Диаграмма, заимствованная из [1] показывает отношения между компонентами паттерна:

Области применения паттерна

Реализация

Теперь, рассмотрим, как этот паттерн можно применять при разработке мобильных приложений, а конкретно, Android–приложений. Способов реализации много, можно использовать IntentService или HaMeR-фрэймворк (Handler, Messages, Runnable) Давайте рассмотрим, как я имплементировал данный паттерн в тестовом приложении. Итак, тестовое приложение показывает маршруты и список мест, которые содержатся в том или ином маршруте. Соответственно, у нас есть два типа запросов (команд): TracksRequest и PlacesRequest. Оба класса являются наследниками базового класса CommonRequest, для того чтобы обрабатываться нашим процессором (CommandProcessor).

В методе sendAsyncPlaceRequest происходит вся работа: это может быть создание URL для запроса к API, создание нового Thread, парсинг ответа и передача результата работы контроллеру с помощью Handler.

Далее следует реализовать класс CommandProcessor, который будет управлять нашими запросами и выполнять их:

Теперь нам нужен котроллер, который, в зависимости от состояния будет отправлять разные команды процессору. Результат работы запроса отправляется из потока с помощью Handler:

Для того, чтобы теперь вернуть результат работы в активити, и вызвать какой-нибудь updateUI() для обновления пользовательского интерфейса (наполнения ListView, отрисовка маркеров на карте и т.д.) нужно определить интерфейс UpdateCallbackListener:

И реализовать его в нашей активити:

После того, как результат вернется в ответе на запрос (к примеру, запрос на получение всех мест по этому маршруту) нам необходимо обновить актвити и передать объекты Place в адаптер. Это мы можем сделать через метод processor_.updateActivity(places), который вызовет onUpdate() в активити, которая имплементировала данный метод. Следующая диаграмма, также взята из [1] показывает динамику поведения паттерна:

Чтобы инициировать запрос, нам нужно создать в активити объект TracksRequestи передать его в controller:

Реализация с помощью IntentService

Использование IntentService также отлично позволяет реализовать этот паттерн. Рассмотрим диаграмму:

В качестве объекта-команды можно использовтаь Intent и передавать его в наш процессор. Creator — это наша activity, которая создает объект-команду, и передает этот объект executor’у, то есть IntentService’у в нашем случае. Таким образом, роль CommandProcessor выполняет класс CustomIntentService, а именно метод onHandleIntent() который в зависимости от данных, содержащихся в Intent, может выполнять различные операции. Чтобы вернуть результат в активити, в данном случае можно использовать BroadcastReceiver.

Читайте также:  Лучший смартфон для андроид авто 2021

Пошаговая инструкция

Итак, подведем итоги, чтобы реализовать данный паттерн необходимо выполнить следующее:

  • Определить интерфейс абстрактной команды. Интерфейс инкапсулирует реализацию каждой отдельной команды. В нашем случае это был метод sendRequest(int i) который каждая из команд (запросов) реализовывала по – разному.
  • Определить способ, которым команда вернет результат тому, кто её вызвал. Как было показано для этого мы определили интерфейс UpdateCallbackListener, с методом onUpdate() для каждой из активити.
  • Реализовать каждую из команд. У нас было два вида запросов – один для получения информации о маршрутах (TracksRequest), второй для получения информации о местах(PlacesRequest).
  • Реализовать контроллер, который будет отправлять команды. Создание и отправление команд можно реализовать с помощью Abstract Factory или Prototype. Однако не обязательно, чтобы контроллер был создателем команд. Как было видно из примера, объекты запросов были созданы в активти.
  • Реализовать класс процессора, который будет принимать команду и обрабатывать. Для каждого из запросов класс RequestProcessor выполняет метод execute() .

Достоинства и недостатки паттерна.

  • В таких приложениях как текстовый редактор, различные элементы интерфейса могут использовать одни и те же команды. Например, кнопка в меню “Создать”, и кнопка с таким же названием в контекстном меню могут обращаться к одной и той же команде.
  • Возможность отправлять асинхронные запросы, управлять порядком вызовов, в зависимости от результата запроса.
  • Гибкость при добавлении нового функционала в виде новой команды (запроса), не нарушая уже существующий функционал. Изменение или добавление новой команды никак не повлияет на обработчик команд.
  • Разные клиенты (в нашем случае активити) могут использовать одни и те же команды. К примеру, запрос на загрузку изображения по Id, может использоваться для разных активити.

Недостатки:

  • Дополнительно нужно отслеживать каждое состояние и соответствующе его обрабатывать.
  • Количество команд может быть очень большим
  • Двусторонняя связь. После того, как активити инициировала создание запроса, и ответ был получен, необходимо результат вернуть в активити.

Заключение

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

Источник

Android Architecture Patterns

When developers work on a real mobile application whose nature is dynamic and will expand its features according to the user’s need, then it is not possible to write core logic in activities or fragments. To structure the project’s code and to give it a modular design(separated code parts), architecture patterns are applied to separate the concerns. The most popular android architectures used by developers are the following:

  • MVC (Model — View — Controller)
  • MVP (Model — View — Presenter)
  • MVVM (Model — View — ViewModel)

The main idea of all these patterns is to organize the project in a proper way so that all the codes get covered in the Unit Testing. Moreover, it is very helpful in the maintenance of the software, to add and remove features and developers can keep a track of various crucial logic parts.

The Model—View—Controller(MVC) Pattern

MVC pattern is the oldest android app architecture which simply suggests separating the code into 3 different layers:

  • Model: Layer for storing data. It is responsible for handling the domain logic(real-world business rules) and communication with the database and network layers.
  • View: UI(User Interface) layer. It provides the visualization of the data stored in the Model.
  • Controller: Layer which contains core logic. It gets informed of the user’s behavior and updates the Model as per the need.

In MVC schema, View and Controller both depend upon the Model. Application data is updated by the controller and View gets the data. In this pattern, the Model could be tested independently of the UI as it is separated. There are multiple approaches possible to apply the MVC pattern. Either the activities and fragments can act like the controller where they are responsible for data processing and updating the views. Another way is to use the activities and fragments as Views and the controller, as well as Models, should be a separate class that does not extend any Android class. If the Views respect the single responsibility principle then their role is just to update the Controller for every user event and just display data from the Model, without implementing any business logic. In this case, UI tests should be enough to cover the functionalities of the View.

Читайте также:  Взлом инди кот для андроид

Advantages:

  • MVC pattern increases the code testability and makes it easier to implement new features as it highly supports the separation of concerns.
  • Unit testing of the Model and Controller is possible as they do not extend or use any Android class.
  • Functionalities of the View can be checked through UI tests if the View respect the single responsibility principle(update controller and display data from the model without implementing domain logic)

Disadvantages:

  • Code layers depend on each other even if MVC is applied correctly.
  • No parameter to handle UI logic i.e., how to display the data.

The Model—View—Presenter(MVP) Pattern

MVP pattern is the second iteration of Android app architecture. This pattern is widely accepted and is still recommended for upcoming developers. The purpose of each component is easy to learn:

  • Model: Layer for storing data. It is responsible for handling the domain logic(real-world business rules) and communication with the database and network layers.
  • View: UI(User Interface) layer. It provides the visualization of the data and keep a track of the user’s action in order to notify the Presenter.
  • Presenter: Fetch the data from the model and applies the UI logic to decide what to display. It manages the state of the View and takes actions according to the user’s input notification from the View.

In the MVP schema, View and Presenter are closely related and have a reference to each other. To make the code readable and easier to understand, a Contract interface class is used to define the Presenter and View relationship. The View is abstracted and has an interface in order to enable the Presenter for Unit Testing.

Advantages:

  • No conceptual relationship in android components
  • Easy code maintenance and testing as the application’s model, view, and presenter layer are separated.

Disadvantages:

The Model—View—ViewModel (MVVM) Pattern

The third iteration of android architecture is the MVVV pattern. While releasing the Android Architecture Components, the Android team recommended this architecture pattern. Below are the separate code layers:

  • Model: This layer is responsible for the abstraction of the data sources. Model and ViewModel work together to get and save the data.
  • View: The purpose of this layer is to inform the ViewModel about the user’s action.
  • ViewModel: It exposes those data streams which are relevant to the View.

The MVVM and MVP patterns are quite similar because both are efficient in abstracting the state and behavior of the View layer. In MVVM, Views can bind itself to the data streams which are exposed by ViewModel.

In MVVM schema the View informs the ViewModel about various actions. The View has a reference to the ViewModel while ViewModel has no information about the View. The many-to-one relationship that exists between View and ViewModel and MVVM supports two-way data binding between both.

Источник

Оцените статью