Введение в проблематику архитектуры iOS приложений

Fade-in...

iOS, MVC, MVVM, MVP, VIPER


В разработке программного обеспечения, как и в математическом моделировании или в любой другой области науки, которая имеет дело с созданием чего-то нового на основе имеющихся знаний, можно выделить несколько этапов. Неудивительно, что разработка ПО, так же как и разработка математической модели, начинается с постановки задачи.
Зачастую, от разработчика не зависит этот этап. Команда получает в каком-то виде описание проектируемой системы. Затем следует анализ и оценка требований к системе. На этапе анализа должны быть учтены риски, выбран стек технологий, вектор направления разработки. Затем, совокупность этих параметров должна быть оценена во временном эквиваленте, который трансформируется в денежный эквивалент. Так работает большинство аутсорсинговых IT-агенств по всему миру. Ошибка на этапе планирования сводит на нет всю последующую работу и может вести за собой пагубные последствия для фирмы как в финансовом, так и в имиджевом отношении.
На этом этапе очень важно, чтобы человек, оценивающий проект, обладал видением системного архитектора. Если оценка каких-то специализированных UI моментов может иметь какую-то погрешность и, в целом, в некоторых случаях, может обсуждаться на этапе разработки (особенно в Agile), то ошибка в выборе архитектуры будет скорее всего фатальной для проекта.
Программирование имеет богатую историю и были созданы типовые переиспользуемые шаблоны проектирования приложений. Эти шаблоны имеют ряд классификаций, но особняком стоят именно архитектурные шаблоны, которые задают направление развития проекта в целом.

Проектирование архитектуры приложений


За относительно недолгую, но насыщенную историю программирования были выведены несколько принципиальных архитектурных шаблонов проектирования приложений - MVC, MVP, MVVM и позднее присоединившийся к ним VIPER (специфичный для iOS, но в настоящее время приобретающий распространение и на Android). Все эти шаблоны говорят об одном и том же, отличаясь друг от друга некоторыми нюансами взаимодействия структурных частей между собой. Общая направленность этих паттернов такова, что в любых приложениях, построенных по данным моделям должно обеспечиваться разделение данных от UI.
Шаблон MVC был разработан еще во времена расцвета таких языков программирования, как SmallTalk и C. В книге, известной в кругах разработчиков как «Банда четырех», все шаблоны проектирования, описанные в той книге описываются уже в контексте использования MVC, об этом говорится во введении книги.
Соблюдение правил MVC позволяет обеспечить модульность, тестируемость и более легкую обслуживаемость приложения.

На этапе проектирования нужно выделить наиболее общие моменты, которые в дальнейшем можно будет переиспользовать (элементы GUI, детали работы с данными, структуры данных и т.д.) Здесь необходимо мыслить абстракциями и уметь находить их в семантике приложения. При правильном подходе и использовании шаблонов проектирования можно легко абстрагироваться даже от веб-сервисов, баз данных и использовать подставные данные. Подмена способа получения данных при этом никак не скажется на остальной части приложения.


Концепция MVC


Концепция Model-View-Controller (Модель-Представление-Контроллер) – схема разделения данных приложения, графического интерфейса пользователя и управляющей логики на три отдельных компонента: модель, представление и контроллер таким образом, что модификация каждого компонента может осуществляться независимо
Модель предоставляет данные и методы работы с ними: запросы в базу данных, проверка на корректность. Модель не зависит от представления, т.е. не знает как данные визуализируются. Модель так же не имеет зависимости от контроллера, т.е. не имеет точек взаимодействия с пользователем. Модель предоставляет доступ к данным и управлению ими.
Модель строится таким образом, чтоб отвечать на запросы, изменяя своё состояние, при этом может быть встроено уведомление «наблюдателей».
Модель, за счёт независимости от визуального представления, может иметь несколько различных представлений для одной «модели».
Ответственность представления заключается в получении необходимых данных из модели и отправке их пользователю. Представление не обрабатывает введённые данные пользователя.
Представление может влиять на состояние модели сообщая модели об этом.
Контроллер обеспечивает «связь» между пользователем и системой. Контролирует и направляет данные от пользователя к системе и наоборот. Использует модель и представление для реализации необходимого действия.
Поскольку MVC не имеет строгой реализации, то реализован он может быть по-разному. Нет общепринятого определения, где должна располагаться бизнес-логика. Она может находиться как в контроллере, так и в модели. В последнем случае, модель будет содержать все бизнес-объекты со всеми данными и функциями.
Некоторые фреймворки жестко задают где должна располагаться бизнес-логика, другие не имеют таких правил.
Также не указано, где должна находиться проверка введённых пользователем данных. Простая валидация может встречаться даже в представлении, но чаще они встречаются в представлении или модели.
Интернационализация и форматирование данных также не имеет четких указаний по расположению.
На рисунке 1 показана классическая схема MVC.
https://lh6.googleusercontent.com/l8w17234wJMp7oB5oj54QoAZIydv4lCyGq7LILANeZ95rAsCDr1t0F62tXJ4PfOq1fUkUMiEjPWjerLliFXznzWQ6KEJPkvAnj2fdlCdbH9mna-UHLpm2YKyD2QAplfSVHiPspkI
Рисунок 1 – Классическая схема MVC
Как видно из рисунка 1, классическая схема действительно не объясняет реализацию всех связей между слоями приложения. Более того наблюдаются свзяи между всеми тремя слоями приложения. При таком подходе не удается в нужной степени разорвать связи между сущностями и переиспользование кода будет минимальным.
Каждая платформа, использующая MVC имеет свои детали реализации. ASP.NET MVC (во много схожа реализация с тем, что показано на рисунке 1.) отличается от Cocoa MVC от Apple. На рисунке 2 представлена схема Cocoa MVC.


https://lh3.googleusercontent.com/X5c0gHXpdB7FJxj7cjOWBvN0ZkajYxejiIHU4gQ1iQSeCX1STvtkRUmY23iLkj1K42Uclvq6VjbZY9t0Z2RLGhaO5cM4bfyNq9IZyA5O4jLSuwwLL3ouxEuWpi1kP6QhtzNNQ1e_
Рисунок 2 – Схема Cocoa MVC
Как видно из рисунка 2, в отличие от схемы, представленной на рисунке 1, здесь разорвана связь между представлением и моделью. По факту Cocoa MVC в реальных условиях зачастую выглядит как на рисунке 3.
https://lh4.googleusercontent.com/wTjjj1VorJrdvJUREwYS6Hmh-D6CQWqAWB-pLUwCLW-3bobY2V5ar9DaTiHDMR0zkzRD2wWyzh-gYPYpBTuV4YPUC8_w_3kzn8v1RIP04WjyJ_kJoNExJkMstE2JWx2YZRpgRX3U
Рисунок 3 – Реальная схема Cocoa MVC
Среди разработчиков под iOS и MacOS часто ходят разговоры о MVC как о Massive View Controller. Проблема видна на рисунке 3. Сильная связанность между отображением и контроллером влечет за собой проблемы расширяемости, переиспользуемости и большое количество кода в классах контроллеров. Контроллер приобретает ответственности, ему не присущие К сожалению, это то, что советуют Apple в своих туториалах.
Единственным выходом в такой ситуации для разработчика является разнесение логики до вида, похожего на схему с рисунка 2. Необходимо облегчить контроллеры по максимуму. Выделять отдельные классы для получения и манипуляции данными. Также необходимо соблюдать один из основополагающих принципов ООП – «Program to an interface, not an implementation», т.е. оперировать надо интерфейсами, а не реализациями. В этом кроется еще один из основополагающих принципов проектирования – black box, т.е. работая с поведенческими структурами, мы не знаем детали реализации.
Как бы то ни было, MVC отлично подходит для RAD (Rapid Applicaton Development).

Концепция MVP

Шаблон MVP (Mode-View-Presenter) – производный от MVC, который используется в основном для построения пользовательских интерфейсов.
Элемент Presenter в данном шаблоне берёт на себя функциональность посредника (аналогично контроллеру в MVC) и отвечает за управление событиями пользовательского интерфейса так же, как в других шаблонах обычно отвечает представление.
На рисунке 4 показана классическая схема MVP
Рисунок 4 – Классическая схема MVP
Шаблон MVP является классическим для проектов на Windows Forms и native Android проектов.
На рисунке 5 представлена интерпретация шаблона MVP от Apple.
https://lh5.googleusercontent.com/WnESb3Ygfl2dLF3dItVWYO5Q7-XBApVTnGjEZP19GYivsC5AW1yUnlBaWcibhiOWC4g1gwlGug5FfzdBL-ciz-KeLoa6DBuHXf9chY_wNS-a6JXBYl66Odp5wxp8KkRSwYEvbQKH
Рисунок 5 – Интерпретация шаблона MVP от Apple
Слой Presenter здесь является абстракцией над View, который управляет отображением, но не содержит в себе детали реализации отображения. Также, презентер являтся посредником между данными и отображением. Классическая реализация MVP предусматривает использование механизма bindings для связки данных и отображения. Однако в этом и заключается фатальный недостаток, т.к. основной целью явяется разрыв излишнего связывания слоев приложения.

Концепция MVVM

Другим паттерном семейства MV-X является паттерн MVVM. Разработчикам .Net, как правило, приходится сталкиваться с именно с этой вариацией шаблона MVC. Этот шаблон проектирования является общепринятым для разработки и архитектуры приложений Windows Phone и WPF.
Паттерн MVVM делится на три части:
  1. модель (Model), так же, как в классической MVC, Модель представляет собой фундаментальные данные, необходимые для работы приложения;
  2. вид/представление (View) — это графический интерфейс, то есть окно, кнопки и.т.п. Вид является подписчиком на событие изменения значений свойств или команд, предоставляемых Моделью Вида. В случае, если в Модели Вида изменилось какое-либо свойство, то она оповещает всех подписчиков об этом, и Вид в свою очередь запрашивает обновленное значение свойства из Модели Вида. В случае если пользователь воздействует на какой-либо элемент интерфейса, Вид вызывает соответствующую команду, предоставленную Моделью Вида;
  3. модель вида (ViewModel, что означает «Model of View») является с одной стороны абстракцией Вида, а с другой предоставляет обертку данных из Модели, которые подлежат связыванию. То есть она содержит Модель, которая преобразована к Виду, а также содержит в себе команды, которыми может пользоваться Вид, чтобы влиять на Модель.
На рисунке 5 показано устройство шаблона MVVM.
ьммь
Рисунок 5 – Шаблон Model – View - ViewModel
Для кроссплатформенной имплементации этого шаблона, лучшей библиотекой является MvvmCross. В ней присутствуют абстрактные классы и интерфейсы, необходимые для обеспечения целостности концепции MVVM. В нативной же разработке для реализации такого архитектурного шаблона используются rxJava для Android и Reactive Cocoa для iOS и MacOS.
Таким образом паттерны MVVM и MVP во многом схожи. Однако, для MVVM связывание представления с моделью отображения осуществляется автоматически, а для шаблона MVP такую связь приходится программировать. MVC имеет больше возможностей по управлению представлением.
Поддержка MVVM в iOS - не самая тривиальная задача и зачастую подразумевает под собой освоение стека ReactiveCocoa.

Концепция VIPER

Данный архитектурный шаблон появился сравнительно недавно и является логическим продолжением MV-X семейства. Этот паттерн призван повысить уровень абстракции, снизить связанность кода и решить проблемы маршрутизации в приложении.
Приложение в подходе VIPER разбивается на модули (а не на экраны) и внешние зависимости.
На рисунке 6 представлена схема шаблона VIPER.
https://lh3.googleusercontent.com/rR7pVMK9WsoNAfl0uAvka3Vof2tb_VTYA3SjPr_ZrTz1VinBHZjcGeIxQFsqrYB8rNIe02CWnv28oX9DWdX6WvYAW4o0aQDfX0XKundFbwTyWceFUcrqwzkI2mOh-ytTlzjz4zZs
Рисунок 6 – Схема шаблона VIPER
Как видно из рисунка 6 такой подход разбивает приложение на большее количество слоев, чем архитектурные предшественники.
  • Interactor содержит бизнес-логику, связанную с данными (Entities): например, создание новых экземпляров сущностей или получение их с сервера. Для этих целей необходимо использовать отдельные классы-менеджеры или классы-сервисы, которые рассматриваются как внешние зависимости, а не как часть модуля VIPER;
  • Presenter содержит бизнес-логику, связанную c UI (но не относящуюся к зависимому от платформы коду), вызывает методы в Interactor. То есть presenter служит своего рода менеджером по обработке запросов из разных слоев модуля. По сути своей presenter является абстракцией вокруг графической оболочки пользователя. В свою очередь графическая оболочка (слой View) отвечает просьбам презентера посредством реализации интерфейсов (классически имеющих постфикс output)), а presenter отвечает интерактору. Таким образом у нас в модуле выстраивается цепочка обязанностей, не ограничивая нас при этом какой-то большой связанностью сущностей, т.к. везде используются интерфейсы, а не реализации;
  • Entities — простые объекты данных, не являются слоем доступа к данным, потому что это ответственность слоя Interactor. По сути, это просто класс, который описывает семантическую единицу приложения. В случае приложения “Курсы валют” это может быть класс Bank, в котором содержится информация о банке (его идентификатор, наименование, url и т.д.). Класс банка не содержит никакой логики, кроме может быть логики конвертации из JSON в объект (некоторые нативные библиотеки требуют такого подхода, это не является большой архитектурной проблемой, хотя можно и выносить это в фабрику)’
  • Router несет ответственность за переходы между VIPER-модулями. Таким образом router - это обычный класс, который будет содержать в себе методы типа : “покажи экран такой-то, надели его при этом параметрами такими-то”.
Если мы сравним VIPER с паттернами MV-X вида, то увидим несколько отличий в распределении обязанностей:
  • логика из Model (взаимодействие данных) смещается в Interactor, а также есть Entities — структуры данных, которые ничего не делают;
  • из Controller, Presenter, ViewModel обязанности представления UI переехали в Presenter, но без возможности изменения данных;
  • VIPER является первым шаблоном, который пробует решить проблему навигации, для этого есть Router.

Основные достоинства и недостатки VIPER

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

  • Резкое увеличение количества классов в проекте, сложности при создании нового модуля.
  • Некоторые из принципов не ложатся напрямую на UIKit и подходы Apple.
  • Отсутствие в открытом доступе набора конкретных рекомендаций, best practices и примеров сложных приложений.

Применение VIPER архитектуры на примере главного экрана приложения «Курсы валют»

На рисунке 7 представлена унифицированная для всех платформ версия главного экрана приложения «Курсы валют».
android_main_framed_710.png
Рисунок 7 – Главный экран приложения «Курсы валют»
Попробуем примерно разобрать этот экран на составляющие VIPER архитектуры.
Выделим слой маршрутизации – BanksRouter. В нем будет осуществляться обработка навигационных сценариев. Такой класс должен уметь переводить пользователя на экран деталей банка, на экран ЦБ РФ, а также на экран выбора конвертируемой валюты. На рисунке 9 показан класс BanksRouter в нотации UML 2.0.
BanksRouter.png
Рисунок 9 – Диаграмма класса BanksRouter
Следующим слоем будет interactor. В VIPER архитектуре интерактор отвечает за взаимодействие с внешними зависимостями-сервисами, файловыми менеджерами, API и т.д. В данном случае в интеракторе должны содержаться методы получения курсов валют в выбранном городе, а также методы сортировки банков по заданному критерию. На рисунке 10 изображена схема класса BanksInteractor в нотации UML2.0.
BanksInteractor.png
Рисунок 10 – схема класса BanksInteractor
Следующим элементом VIPER будет presenter – абстракция вокруг слоя View, не имеющая представления о платформенной реализации View, являющаяся менеджером, который координирует взаимодействие interactor-а, данных, отображения и пользователя. Классически, presenter общается с View и Interactor посредством интерфейсов. В идеологии VIPER такие интерфейсы имеют постфикс output. Так, в классическом понимании VIPER, presenter должен реализовывать интерфейс interactorOutput, а слой view – реализовывать интерфейс presenterOutput. В presenter должны храниться ссылки на view, router, interactor, а также на presenter output(хотя view и может реализовывать output presenter-а, это не является исключительным и обязательным условием, реализовывать интерфейс может и дополнительный слой или класс-помощник). В некоторых случаях presenter может иметь состояние (state). В сложных случаях рекомендуется выносить состояние в отдельный класс, тогда presenter должен хранить еще и объект состояния.
Слой View обладает высокой специфичностью, напрямую зависимой от платформы, для которой реализуется. В данной работе он не будет рассмотрен. На рисунке 11 показана диаграмма классов модуля Banks, построенного в VIPER стиле.
../Documents/BanksModule.png
Рисунок 11 – Диаграмма классов модуля Banks

Fade-out...


В данной работе кратко рассмотрены основные архитектурные подходы в разработке приложений. Приведена краткая антология развития идеологии MV-X, а также описан идеологический потомок семейства MV-X – VIPER. Также, приведены достоинства и недостатки VIPER- архитектуры. Затем рассмотрено построение модуля банков приложения Курсы валют в контексте VIPER-архитектуры.
Спасибо компании Badoo за красивые картинки про MVC, MVP, MVVM, VIPER. Я не нарисовал бы лучше. 🙏

Что почитать:




Comments

Popular posts from this blog

История разработки iOS-версии приложения "Айда!"

VIPER и проблема кодогенерации