Попытка подобрать "чистую архитектуру" в приложении iOS

Недавно я пересматривал проект андроидной архитектуры, пытаясь адаптировать его к более "чистой архитектуре", в частности, к типу дизайна предложенный "Дядя Боб" .

Что это связано с несколькими уровнями абстракций, хорошей изоляцией обязанностей и очень сильной инверсией зависимостей, достигаемой путем инъекции зависимостей; что, в конечном счете, приводит к очень развязанной переносной системе. Идеальный кандидат для тестирования с помощью модульного тестирования и тестирования интеграции.

В моей реализации Android я получил три разных модуля или слоя:

- домен: сущности, интеракторы, ведущие (чистый Java-модуль)

- data​​strong > : (действует как репозиторий для доставки данных в домен) (модуль библиотеки Android)

- презентация: связанные с ui вещи, фрагменты, действия, представления и т.д. (модуль приложений для Android)

Итак, я пытаюсь выяснить, какой будет лучший подход к экосистеме iOS. Ive попытался создать проект с несколькими целями для достижения того же решения:

- домен: целевая строка командной строки (что кажется очень странным, но я считаю, что это самая чистая доступная цель)

- данные: cocoa сенсорная структура

- презентация: cocoa сенсорная структура

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

Но мои знания очень ограничены в проектах с несколькими целями. Я имею в виду, что Ive никогда не создавал приложение iOS с несколькими целями. Поэтому я не знаю, даже если решение будет использовать фреймворк (cocoa touch/ cocoa) в качестве цели вместо модуля командной строки для уровня домена.

Любая мысль была бы действительно оценена.

Спасибо!

Ответ 1

Дядя Боб Чистая архитектура абсолютно применима к iOS, Swift и Obj-C. Архитектура является языковой агностикой. Дядя Боб сам кодирует в основном на Java, но в своих разговорах он редко упоминает Java. Все его слайды даже не показывают никакого кода. Это архитектура, предназначенная для любого проекта.

Почему я так уверен? Потому что я изучал MVC, MVVM, ReactiveCocoa и Clean Architecture в течение 2 лет. Я, как чистая архитектура, лучше всего. Я протестировал его, переведя 7 образцов Apple на использование чистой архитектуры. Я использовал этот подход исключительно более года. Он работает лучше каждый раз.

Некоторые из преимуществ:

  • Найти и исправить ошибки быстрее и проще.
  • Извлеките бизнес-логику из контроллеров представлений в интеракторы.
  • Извлечение логики представления из контроллеров представления в презентаторы.
  • С уверенностью меняйте существующее поведение с быстрыми и поддерживаемыми модульными тестами.
  • Напишите более короткие методы с единой ответственностью.
  • Разделить зависимости классов с четкими установленными границами.

Мы также добавили компонент маршрутизатора, чтобы мы могли использовать несколько раскадровки. Больше конфликтов.

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

Я написал свой опыт за последние 2 года, изучая архитектуру iOS в Clean Swift. Я также собрал некоторые шаблоны Xcode для создания всех компонентов Clean Architecture для сохранения тонны времени.

ОБНОВЛЕНИЕ. Чтобы ответить на вопрос Vizctor Albertos об инъекции зависимостей в комментарии ниже.

Это действительно большой вопрос и требует подробного ответа.

Всегда помните VIP-цикл. В этом случае метод doSomethingOnLoad() не является граничным методом. Скорее, это внутренний метод, вызываемый только внутри CreateOrderViewController. В модульном тестировании мы тестируем ожидаемое поведение устройства. Мы даем входные данные, наблюдаем выходы, затем сравниваем результаты с нашими ожиданиями.

Да, я мог бы сделать doSomethingOnLoad() приватный метод. Но я решил не делать этого. Одна из целей Swift заключается в том, чтобы разработчикам было проще писать код. Все граничные методы уже перечислены во входных и выходных протоколах. Нет необходимости засорять класс внешними частными модификаторами.

Теперь нам нужно проверить это поведение: "CreateOrderViewController должен что-то делать с загрузкой с данными запроса", так или иначе? Как мы тестируем это, если мы не можем вызвать doSomethingOnLoad(), потому что это частный метод? Вы вызываете viewDidLoad(). Метод viewDidLoad() является граничным методом. Какая граница? Граница между пользователем и контроллером просмотра! Пользователь сделал что-то с устройством, чтобы загрузить его на другой экран. Итак, как же мы тогда вызываем viewDidLoad()? Вы делаете это так:

let bundle = NSBundle(forClass: self.dynamicType)
let storyboard = UIStoryboard(name: "Main", bundle: bundle)
let createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController
let view = createOrderViewController.view

Просто вызов свойства createOrderViewController.view вызовет вызов viewDidLoad(). Я узнал этот трюк давным-давно от кого-то. Но Наташа Робот также недавно упомянула об этом.

Когда мы решаем, что тестировать, очень важно только проверять граничные методы. Если мы тестируем каждый метод класса, тесты становятся чрезвычайно хрупкими. Каждое изменение, которое мы делаем для кода, приведет к разрыву многих, многих тестов. Из-за этого многие люди отказываются.

Или, подумайте об этом так. Когда вы спрашиваете, как издеваться CreateOrderRequest, сначала спросите, является ли doSomethingOnLoad() граничным методом, для которого вы должны написать тест. Если нет, то что? В этом случае граничный метод фактически viewDidLoad(). Вход "когда этот просмотр загружается". Результатом является "вызов этого метода с помощью этого объекта запроса".

Это еще одно преимущество использования Clean Swift. Все ваши граничные методы перечислены в верхней части файла с явно указанными протоколами CreateOrderViewControllerInput и CreateOrderViewControllerOutput. Вам не нужно искать в другом месте!

Подумайте, что произойдет, если вы проверили doSomethingOnLoad(). Вы издеваетесь над объектом запроса, а затем утверждаете, что он равен ожидаемому объекту запроса. Вы издеваетесь над чем-то и сравниваете это. Это как assert(1, 1) вместо var a=1; assert(a, 1). Какой смысл? Слишком много тестов. Слишком хрупкий.

Теперь, когда вы делаете mock CreateOrderRequest. После того, как вы подтвердите правильность, CreateOrderRequest может быть сгенерирован компонентом контроллера вида. Когда вы тестируете граничный метод CreateOrderInteractor doSomething(), вы затем обманываете CreateOrderRequest с помощью инъекции зависимостей интерфейса.

Короче говоря, модульное тестирование - это не тестирование каждой единицы класса. Речь идет о тестировании класса как единицы.

Это сдвиг мышления.

Надеюсь, что это поможет!

У меня есть 3 серии статей в Wordpress на разные темы:

  • Углубленный просмотр каждого компонента Clean Swift.
  • Как разбить сложную бизнес-логику на рабочих и служебных объектов.
  • Написание тестов в архитектуре iOS Clean Swift

Какой из них вы хотите услышать больше в первую очередь? Должен ли я поднять серию на тестирование?

Ответ 2

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

https://yoichitgy.github.io/post/dependency-injection-in-mvvm-architecture-with-reactivecocoa-part-2-project-setup/