Подтвердить что ты не робот

Чистая архитектура: как отразить изменения уровня данных в пользовательском интерфейсе

Я пытаюсь сделать дизайн на основе Uncle Bob Clean Architecture в Android.

Эта проблема:

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

Пример

Я разработал ОЧЕНЬ упрощенный пример для этого примера. Обратите внимание, что граничные интерфейсы были удалены, чтобы сохранить небольшие диаграммы.

Представьте себе приложение, которое показывает список видео (с заголовком, номером и т.д.), Щелкая видео, чтобы увидеть детали (там вы можете полюбить/не полюбить видео).

Кроме того, в приложении есть система статистики, которая подсчитывает количество видео, которые понравились или не понравились пользователю.

Основными классами для этого приложения могут быть:

Для части/модуля "Видео": enter image description here

Для части/модуля Stats: enter image description here

Цель

Теперь представьте, что вы проверяете свою статистику, затем перемещаетесь по списку видео, открываете детали одного и нажимаете кнопку "Нравится".

После того, как подобное отправлено на сервер, есть несколько элементов приложений, которые должны знать об изменении:

  • Конечно, подробный вид должен быть обновлен с изменениями (это может быть сделано с помощью обратных вызовов, так что нет проблем)
  • Список видео должен обновлять количество лайков для данного видео.
  • StatsRepository может захотеть обновить/аннулировать кэши после голосования нового видео
  • Если список статистики виден (представьте разделенный экран), он также должен показать обновленную статистику (или, по крайней мере, получить событие для повторного запроса данных)

Вопрос

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

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

4b9b3361

Ответ 1

Опубликовать/Подписать

Как правило, для связи n: m (n отправителей могут отправлять сообщение m-приемникам, в то время как все отправители и получатели не знают друг друга), вы будете использовать публикацию/подписку шаблон. Существует множество библиотек, реализующих такой стиль общения, для Java есть, например, реализация EventBus в библиотеке Guava. Для общения в приложении эти библиотеки обычно называются EventBus или EventManager и событиями отправки/получения.

События домена

Предположим, что вы создали событие VideoRatedEvent, которое сигнализирует, что пользователь либо любил, либо не любил видео. Эти типы событий называются События домена. Класс события - это просто POJO и может выглядеть так:

class VideoRatedEvent {
    /** The video that was rated */
    public Video video;
    /** The user that triggered this event */
    public User user;
    /** True if the user liked the video, false if the user disliked the video */
    public boolean liked;
}

События отправки

Теперь каждый раз, когда вашим пользователям нравится или не нравится видео, вам нужно отправить VideoRatedEvent. С помощью Guava вы просто передадите экземпляр объекта события для объекта EventBus.post(myVideoRatedEvent). В идеале события генерируются в ваших объектах домена и отправляются в рамках сохраняющейся транзакции (см. этот пост в блоге). Это означает, что по мере сохранения состояния модели домена события отправляются.

Слушатели событий

В вашем приложении все компоненты, подверженные событию, теперь могут прослушивать события домена. В вашем конкретном примере VideoDetailView или StatsRepository могут быть прослушивателями событий для VideoRatedEvent. Конечно, вам нужно будет зарегистрировать их в Guava EventBus с помощью EventBus.register(Object).

Ответ 2

Это мои личные 5центы и, возможно, недостаточно тесно связаны с вашим примером "The Clean Architecure".

Обычно я пытаюсь заставить своего рода MVC выполнять действия и фрагменты андроидов и использовать публикацию/подписку для общения. В качестве компонентов у меня есть классы моделей, которые обрабатывают бизнес-логику и состояние данных. Эти методы изменения данных должны вызываться только классами контроллера, которые обычно являются классом активности, а также обрабатывают состояние сеанса. Я использую фрагменты для управления различными частями обзора приложения и представлениями под этими фрагментами (очевидно). Все фрагменты подписываются на одну или несколько тем. Я использую свой простой DataDistributionService, который обрабатывает разные темы, принимает сообщения от зарегистрированных издателей и передает их всем подписчикам. (частично под влиянием OMGs DDS, но MUCH MUCH более примитивным). Простое приложение будет иметь только одну тему, например. "Main".

Каждая часть взаимодействия с глазами (касания и т.д.) Сначала обрабатывается ее фрагментом. Фрагмент может изменить несколько вещей без отправки уведомлений. Например. переключение поддиапазона отображаемых элементов данных, если остальной части приложения не нужно знать/реагировать. В противном случае фрагмент публикует ViewRequest (...), содержащий необходимые параметры для DDS.

DDS передает это сообщение и в какой-то момент достигает контроллера. Это может быть просто основной актив или конкретный экземпляр контроллера. Должен быть только ОДИН контроллер, чтобы запрос обрабатывался только один раз. Контроллер в основном имеет длинный список кода обработки запросов. Когда запрос поступает, контроллер обращается к бизнес-логике модели. Контроллер также обрабатывает другие связанные с просмотром вещи, такие как упорядочение (вкладки) или начальные диалоги для ввода пользователем (перезаписывать файл?) И другие вещи, о которых модель не должна знать, но влияет (бросить новый NoOverWritePermissionException())

После изменения модели контроллер решает, нужно ли отправлять уведомление об обновлении. (обычно это происходит). Таким образом, классы моделей не должны прослушивать или отправлять сообщения, а только заботиться о логике бизнес-процессов и согласованном состоянии. Уведомление об обновлении передается и принимается фрагментами, которые затем запускаются "updateFromModel()".

Эффекты:
Команды глобальны. Любой запрос ViewRequest или другой вид может быть отправлен из любой точки доступа DDS. Фрагменты не должны предоставлять класс слушателя, и более высокий экземпляр не должен реализовывать слушателей для своих инстанцированных фрагментов. Если новый фрагмент не требует новых запросов, он может быть добавлен без каких-либо изменений в классы контроллера.

Модельным классам вообще не нужно знать о связи. Это может быть достаточно сложно, чтобы поддерживать согласованное состояние и управлять всем управлением данными. Обработка сообщений или обработка сеанса не требуется. Однако модель не может быть защищена от злостных вызовов из представления. Но это общая проблема и не может быть действительно предотвращена, если в какой-то момент модель должна выдавать ссылки. Если ваше приложение в порядке с моделью, которая только передает копии/плоские данные, это возможно. Но в какой-то момент ArrayAdapter просто нужен доступ к растровым изображениям, которые он должен рисовать в gridview. Если вы не можете позволить себе копии, вы всегда рискуете, что "вид меняет вызов модели". Различные битвы...

Обновление вызовов может быть слишком простым. Если обновление фрагмента является дорогостоящим (перезагрузка текстур OpenGL...), вы хотите получить более подробную информацию об обновлении. Контроллер COULD отправляет более подробное уведомление, однако на самом деле не должно/не знать, какие части модели были точно изменены. Отправка замечаний по обновлению из модели является уродливой. Мало того, что модель должна реализовать обмен сообщениями, но она также становится очень хаотичной со смешанными уведомлениями. Контроллер может разделить уведомления об обновлениях и другие, используя темы. Например. определенную тему для изменений ваших видеоресурсов. Таким образом, фрагменты могут решить, какие темы они подписывают. Помимо этого вы хотите иметь модель, которая может быть запрошена для измененных значений. Timestamp и т.д. У меня есть приложение, в котором пользователь рисует фигуры на холсте. Они отображаются в растровых изображениях и используются как текстуры в представлении OpenGL. Я, конечно, не хочу перезагружать текстуры каждый раз, когда "updateFromModel()" вызывается в GLViewFragment.

Правило зависимости:
Наверное, не уважали все время.Если контроллер управляет переключателем табуляции, он может просто вызвать "seletTab()" на TabHost и, следовательно, иметь зависимость от внешних кругов. Вы можете превратить его в сообщение, но тогда оно все еще является логической зависимостью. Если часть контроллера должна организовать некоторые элементы представления (автоматически покажите вкладку image-editor-fragment-insert после загрузки изображения через закладку-gallery-fragmen-tab), вы не сможете полностью избежать зависимостей. Возможно, вы можете это сделать, смоделировав viewstate, и ваши части представления организуют себя из viewstate.currentUseCase или smth. Но если вам нужен глобальный контроль над представлением вашего приложения, у вас появятся проблемы с этим правилом зависимости, я бы сказал. Что делать, если вы пытаетесь сохранить некоторые данные, и ваша модель запрашивает разрешение на перезаписи? Для этого вам нужно создать какой-то пользовательский интерфейс. Зависимость снова. Вы можете отправить сообщение в представление и надеяться, что DialogFragment подберет его. Если он существует в чрезвычайно модульном мире, описанном в вашей ссылке.

Юридические лица:
 являются модельными классами в моем подходе. Это довольно близко к ссылке, которую вы предоставили.

Случаи использования:
На данный момент у меня нет моделей, явно смоделированных. Atm Я работаю над редакторами для активов в видеоиграх. Рисование фигур в одном фрагменте, применение значений затенения в другом фрагменте, сохранение/загрузка в галереях, экспорт в атлас текстуры в другой... что-то вроде этого. Я бы добавил Use Cases в качестве своего рода подмножества Request. В принципе, пример использования как набор правил, которые запрашивают, в каком порядке разрешены/требуются/ожидаются/запрещены и т.д. Я бы построил их как транзакции, чтобы случай использования мог продолжать прогрессировать, может быть закончен, может быть отменен и, возможно, даже свернут назад. Например. Случай использования определит порядок сохранения нового обрамленного изображения. Включая отправку диалога, чтобы запросить разрешение на перезапись и откат, если разрешение не получено или тайм-аут достигнут. Но случаи использования определяются разными способами. В некоторых приложениях используется один случай использования в течение часа активного взаимодействия с пользователем, в некоторых приложениях используется 50 случаев использования, чтобы получить деньги от атм.;)

Интерфейсные адаптеры:
Здесь это становится немного сложнее. Для меня это, по-видимому, чрезвычайно высокий уровень для приложений для Android. В нем говорится: "Кольцо интерфейсных адаптеров содержит всю архитектуру MVC графического интерфейса". Я не могу обнять вокруг себя эту голову. Возможно, вы создаете гораздо более сложные приложения, чем я.
Рамки и драйверы:
Не уверен, что думать об этом. "Интернет - это подробная информация, база данных - деталь...", и графический файл содержит "UI" в этом кольце. Слишком много для моей маленькой головы

Позволяет проверить другие "утверждения"
Независимо от рамок. Архитектура не зависит от наличия какой-либо библиотеки загруженного программным обеспечением. Это позволяет использовать такие среды, как инструменты, а не для того, чтобы забивать вашу систему в их ограниченные ограничения.
Хорошо, хорошо, если вы запустите свою собственную архитектуру, что вы получите.

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

Независимо от пользовательского интерфейса. Пользовательский интерфейс может легко меняться, не изменяя остальную часть системы. Веб-интерфейс можно заменить консольным интерфейсом, например, без изменения бизнес-правил.
Снова немного перехитрить для андроида, не так ли? Независимость да. В моем подходе вы можете добавлять или удалять фрагменты, если они не требуют явной обработки где-то выше. Но замена веб-интерфейса пользовательским интерфейсом консоли и запуск системы, как и раньше, - это моральная мечта уродцев архитектуры. Некоторые элементы пользовательского интерфейса являются неотъемлемой частью предоставляемой услуги. Конечно, я могу легко поменять фрагмент рисования холста на фрагмент фрагмента консоли или классический фрагмент фотографии для фрагмента "снимок с консолью", но это не значит, что приложение все еще работает. Технически это прекрасно в моем подходе. Если вы реализуете консольный видеопроигрыватель ascii, вы можете отображать видео там, и никакая другая часть приложения не обязательно будет заботиться. Однако МОЖНО быть в том, что набор запросов, поддерживаемых контроллером, плохо выравнивается с новым пользовательским интерфейсом консоли или что пример использования не предназначен для заказа, в котором нужно получить доступ к видео через консольный интерфейс. Представление не всегда является несущественным представляющим рабом, которого многие архитекторы-гуру любят видеть.

Независимо от базы данных. Вы можете обменять Oracle или SQL Server на Mongo, BigTable, CouchDB или что-то еще. Ваши бизнес-правила не привязаны к базе данных.
Да так? Как это напрямую связано с вашей архитектурой? Используйте правильные адаптеры и абстракцию, и вы можете получить это в приветственном приложении мира.

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