В компании Mutual Mobile тестирование является частью создания отличного программного обеспечения. Однако тестирование не всегда было ключевой частью при создании приложений под iOS. Когда мы начали искать способы, чтобы улучшить тестирование наших приложений, то обнаружили, что написание тестов для приложений это довольно сложно. И решили, что если мы собираемся улучшить способ тестирования программного обеспечение, то мы должны сначала придумать лучший способ спроектировать приложения, и это решение мы назвали VIPER.
Традиционным способом проектирования приложения под iOS является использование шаблона MVC (модель-представление-контроллер). Использование MVC для архитектуры приложения, может натолкнуть Вас на мысль, что каждый класс представляет собой модель, или представление, или контроллер. Поскольку значительная часть логики приложения не входит в модель или представление, она обычно оказывается в контроллере. Это приводит к проблеме, известной как Massive View Controllers, где контроллеры в конечном итоге делают слишком много. Если вся логика встроена в контроллер представления, это приводит к тестированию логики через UI, в свою очередь это является неправильным способом проектированиям логики. Также проще совмещать бизнес-логику и UI код в том же методе. Когда Вам будет нужно добавить новые функциональные возможности или исправить ошибку, то будет трудно определить, где внести изменение и при этом быть уверенным, что не будет непредсказуемых последствий в другом месте.
В поиске лучшего способа спроектировать iOS приложение я наткнулся на Clean Architecture, как описал Uncle Bob. Clean Architecture делит логическую структуру приложения на различные уровни обязанностей. Это упрощает изолирование зависимости (например, ваша база данных) и тестирование взаимодействия на границах между уровнями.
VIPER является нашей реализацией Clean Architecture для iOS приложений.
Слово VIPER — бэкроним для View, Interactor, Presenter, Entity и Routing.
Если коротко у вас есть:
Первоначально мои коллеги именовали эту архитектуру как VIP архитектуру. В некотором смысле это нарушение прав, ведь это можно интерпретировать и как “Very Important Architecture”. Так как я не хочу, чтобы люди думали, что другие архитектурные решения не важны, я решил назвать ее VIPER и позже придумал, что E и R будут означать.
Это разделение также соответствует принципу Single Responsibility. Interactor ответствен за бизнес-аналитику, Presenter отвественныей за отображение, и View ответствено за визуальное предствление.
Ниже приведена схема различных компонентов и как они связаны между собой:
Interactor является простым юз кейсом в приложении. Он содержит бизнес-логику для управления объектами (Entity), чтобы выполнить определенную задачу. Задача выполняется в Interactor'е, независимо от любого UI. Тот же Interactor можно использовать в iOS приложениях или консольных приложениях для Mac OS.
Поскольку Interactor является PONSO (Обычный NSObject), который прежде всего содержит логику, и его легко разработать при помощи TDD (Разработка через тестирование).
Entity — это объекты, которыми управляет Interactor. Entity только управляет Interactor. Он никогда не передает сущности уровню представления (т.е. Presenter'у).
Data Store (например, веб-сервис, база данных) отвечает за предоставление Entity в Interactor. Поскольку Interactor применяет свою бизнес-логику, он будет осуществлять выборку Entity из хранилища данных, управлять Entity и затем возвращать обновленные Entity назад в хранилище данных. Хранилище данных управляет персистентностью Entity. Entity не знают о хранилище данных, таким образом, они не знают, как сохраняться.
При использовании TDD (Разработка через тестирование) для разработки Interactor'a, возможно отключить производственное хранилище данных с помощью double/mock тестов. Не обращаясь к удаленному серверу (для веб-сервиса) или диска (для базы данных) позволяет вашим тестам быть повторяемыми и быстрыми.
Presenter — это PONSO, который в основном состоит из логики, чтобы управлять UI. Он собирает входные данные от взаимодействия с пользователем, таким образом, он может отправлять запросы Interactor'у. Presenter также получает результаты Interactor'а и преобразовывать результаты в состояние, которое является наиболее эффективным для отображения на View.
Entity никогда не передаются из Interactor'а к Presenter'у. Вместо этого простые структуры данных, у которых нет поведения, передаются из Interactor'а к Presenter'у. Это препятствует любой ‘реальной работе’ в Presenter'е. Presenter может только подготовить данные для отображения на View.
View является пассивной. Оно ждет Presenter'а, чтобы передать содержания для выведения на экран; она никогда не запрашивает данные у Presenter'а. Методы, определенные для представления (например, LoginView для экрана входа в систему), должны позволить Presenter'у общаться на более высоком уровне абстракции, выраженной с точки зрения его содержимого, а не то, как это содержимое будет отображаться. Presenter не знает о существовании UILabel, UIButton, и т.д. Presenter только знает о содержании, которое он поддерживает и когда его нужно вывести на экран. Presenter нужно определять, как содержание выводиться на экран.
View — это абстрактный интерфейс, определенный в Objective-C с помощью протокола. UIViewController или один из его подклассов реализуют протокол View. Например, это может быть экран входа в систему:
Маршрутизация обрабатывает навигацию от одного экрана к другому, как определено в wireframes, созданных проектировщиком взаимодействия. Wireframe объект несет ответственность за маршрутизацию. Wireframe объект владеет объектами UIWindow, UINavigationController, и т.д. Он ответственен за создание Interactor, Presenter и View/ViewController и за настройки ViewController. Так как Presenter содержит логику, чтобы реагировать на ввод данных пользователем, Presenter знает, когда перейти на другой экран. Wireframe знает, как это сделать. Итак, Presenter — это пользователь Wireframa.
Вы можете найти приложение Counter, это простое приложение, которое демонстрирует использование Interactor, Presenter и View. В следующей статье будет более подробно рассказано о том, как это приложение было разработано. Дополнительные статьи проиллюстрируют использование хранилища данных и Wireframe.
Таким образом, VIPER помогает нам быть более точными, что касается разделения проблем, разделяя большое количество кода одного класса на несколько меньших классов. За счет поддержания единственной ответственности в каждом классе это упростит разработку классов, используя TDD, которое позволяет нам более быстро реагировать на изменяющиеся требования и создавать лучшее программное обеспечение.
Традиционным способом проектирования приложения под iOS является использование шаблона MVC (модель-представление-контроллер). Использование MVC для архитектуры приложения, может натолкнуть Вас на мысль, что каждый класс представляет собой модель, или представление, или контроллер. Поскольку значительная часть логики приложения не входит в модель или представление, она обычно оказывается в контроллере. Это приводит к проблеме, известной как Massive View Controllers, где контроллеры в конечном итоге делают слишком много. Если вся логика встроена в контроллер представления, это приводит к тестированию логики через UI, в свою очередь это является неправильным способом проектированиям логики. Также проще совмещать бизнес-логику и UI код в том же методе. Когда Вам будет нужно добавить новые функциональные возможности или исправить ошибку, то будет трудно определить, где внести изменение и при этом быть уверенным, что не будет непредсказуемых последствий в другом месте.
VIPER
В поиске лучшего способа спроектировать iOS приложение я наткнулся на Clean Architecture, как описал Uncle Bob. Clean Architecture делит логическую структуру приложения на различные уровни обязанностей. Это упрощает изолирование зависимости (например, ваша база данных) и тестирование взаимодействия на границах между уровнями.
VIPER является нашей реализацией Clean Architecture для iOS приложений.
Слово VIPER — бэкроним для View, Interactor, Presenter, Entity и Routing.
Структура VIPER
Если коротко у вас есть:
- Interactor, который содержит бизнес-логику, предусмотренную сценарием.
- Presenter, который содержит логику подготовки содержимого для отображения (полученного из Interactor) и для реакции на ввод данных пользователем (запрашивая новые данные от Interactor).
- View, которое отображает, что сообщил Presenter и передает ввод данных пользователем назад Presenter'у.
Первоначально мои коллеги именовали эту архитектуру как VIP архитектуру. В некотором смысле это нарушение прав, ведь это можно интерпретировать и как “Very Important Architecture”. Так как я не хочу, чтобы люди думали, что другие архитектурные решения не важны, я решил назвать ее VIPER и позже придумал, что E и R будут означать.
Это разделение также соответствует принципу Single Responsibility. Interactor ответствен за бизнес-аналитику, Presenter отвественныей за отображение, и View ответствено за визуальное предствление.
Ниже приведена схема различных компонентов и как они связаны между собой:
Interactor/Интерактор
Interactor является простым юз кейсом в приложении. Он содержит бизнес-логику для управления объектами (Entity), чтобы выполнить определенную задачу. Задача выполняется в Interactor'е, независимо от любого UI. Тот же Interactor можно использовать в iOS приложениях или консольных приложениях для Mac OS.
Поскольку Interactor является PONSO (Обычный NSObject), который прежде всего содержит логику, и его легко разработать при помощи TDD (Разработка через тестирование).
Entity/Сущность
Entity — это объекты, которыми управляет Interactor. Entity только управляет Interactor. Он никогда не передает сущности уровню представления (т.е. Presenter'у).
Data Store/Хранилище данных
Data Store (например, веб-сервис, база данных) отвечает за предоставление Entity в Interactor. Поскольку Interactor применяет свою бизнес-логику, он будет осуществлять выборку Entity из хранилища данных, управлять Entity и затем возвращать обновленные Entity назад в хранилище данных. Хранилище данных управляет персистентностью Entity. Entity не знают о хранилище данных, таким образом, они не знают, как сохраняться.
При использовании TDD (Разработка через тестирование) для разработки Interactor'a, возможно отключить производственное хранилище данных с помощью double/mock тестов. Не обращаясь к удаленному серверу (для веб-сервиса) или диска (для базы данных) позволяет вашим тестам быть повторяемыми и быстрыми.
Presenter/Презентатор
Presenter — это PONSO, который в основном состоит из логики, чтобы управлять UI. Он собирает входные данные от взаимодействия с пользователем, таким образом, он может отправлять запросы Interactor'у. Presenter также получает результаты Interactor'а и преобразовывать результаты в состояние, которое является наиболее эффективным для отображения на View.
Entity никогда не передаются из Interactor'а к Presenter'у. Вместо этого простые структуры данных, у которых нет поведения, передаются из Interactor'а к Presenter'у. Это препятствует любой ‘реальной работе’ в Presenter'е. Presenter может только подготовить данные для отображения на View.
View/Вид
View является пассивной. Оно ждет Presenter'а, чтобы передать содержания для выведения на экран; она никогда не запрашивает данные у Presenter'а. Методы, определенные для представления (например, LoginView для экрана входа в систему), должны позволить Presenter'у общаться на более высоком уровне абстракции, выраженной с точки зрения его содержимого, а не то, как это содержимое будет отображаться. Presenter не знает о существовании UILabel, UIButton, и т.д. Presenter только знает о содержании, которое он поддерживает и когда его нужно вывести на экран. Presenter нужно определять, как содержание выводиться на экран.
View — это абстрактный интерфейс, определенный в Objective-C с помощью протокола. UIViewController или один из его подклассов реализуют протокол View. Например, это может быть экран входа в систему:
@protocol LoginView <NSObject>
- (void)setUserName:(NSString*)userName;
- (void)setPassword:(NSString*)password;
- (void)setLoginEnabled:(BOOL)enabled;
@end
@interface LoginViewController : UIViewController <LoginView>
…
@end
Wireframe/Каркас
Маршрутизация обрабатывает навигацию от одного экрана к другому, как определено в wireframes, созданных проектировщиком взаимодействия. Wireframe объект несет ответственность за маршрутизацию. Wireframe объект владеет объектами UIWindow, UINavigationController, и т.д. Он ответственен за создание Interactor, Presenter и View/ViewController и за настройки ViewController. Так как Presenter содержит логику, чтобы реагировать на ввод данных пользователем, Presenter знает, когда перейти на другой экран. Wireframe знает, как это сделать. Итак, Presenter — это пользователь Wireframa.
Пример
Вы можете найти приложение Counter, это простое приложение, которое демонстрирует использование Interactor, Presenter и View. В следующей статье будет более подробно рассказано о том, как это приложение было разработано. Дополнительные статьи проиллюстрируют использование хранилища данных и Wireframe.
Feedback
Таким образом, VIPER помогает нам быть более точными, что касается разделения проблем, разделяя большое количество кода одного класса на несколько меньших классов. За счет поддержания единственной ответственности в каждом классе это упростит разработку классов, используя TDD, которое позволяет нам более быстро реагировать на изменяющиеся требования и создавать лучшее программное обеспечение.