Flutter — динамично развивающийся фреймворк для написания кроссплатформенных приложений. Несмотря на свою молодость (первая стабильная версия Flutter 1.0 была представлена всего лишь 4 декабря 2018 года, то есть чуть больше года назад на момент публикации статьи), он приобрел немало сторонников, и его популярность только растет. Сегодня мы хотели бы рассказать об одной из самых популярных среди Flutter-разработчиков архитектур, которая была разработана в Google, и называется — BLoC.
Что такое BLoC
Давайте разберемся с основной терминологией. BLoC — это акроним от «Business Logic Component» (компонент бизнес-логики). Как следует из названия, это класс, отделяющий бизнес-логику приложения от пользовательского интерфейса. Такой компонент содержит код, который можно повторно использовать где угодно: в другом модуле, на другой платформе, в другом приложении. Если вы до этого работали с архитектурой MVVM, то можете провести аналогию и сравнить Bloc с ViewModel — они похожи по своему назначению.
Архитектура ПО, MVC и бизнес-логика. Критика Django
Обратите внимание на разницу в написании терминов. Здесь и далее употребляется BLoC, когда речь идет об архитектуре, и Bloc – когда о классе в программе.
Отделение UI от бизнес-логики для Flutter жизненно необходимо. Согласитесь, что карабкаться вверх-вниз по дереву виджетов (UI-компонентов) в поисках нужной вам логики — не самая приятная вещь. Особенно, если верстка и так содержит очень много кода и разбросана по разным файлам.
Кроме того, если мы следуем заветам Clean Architecture (а мы им следуем, не так ли?), то в нашем UI вообще не должно быть ничего лишнего. Равно как и бизнес-логика ничего не должна знать про UI, который к ней обращается. Благодаря такому тщательному разделению ответственностей мы получаем полностью изолированный компонент, который можно легко тестировать независимо от UI и использовать в другом окружении. Этот компонент и есть наш Bloc.
Одной из отличительных особенностей паттерна является то, что он полностью базируется на реактивности. Что это значит? Реактивное программирование — это программирование с асинхронными потоками данных.
В традиционном императивном стиле мы обычно пишем код следующего содержания: вот текстовое поле, назначаем ему обработчик, реагирующий на ввод нового символа, в обработчике добавляем объект, у которого вызываем метод обновления текстового поля в модели. Мы четко описываем каждый шаг. В реактивном подходе все выглядит немного иначе. Текстовое поле связывается с конкретной переменной в модели. И как только пользователь начинает что-то печатать, эта переменная сразу принимает то значение, которое сейчас содержится в текстовом поле.
Для реализации реактивных сценариев язык Dart по умолчанию предлагает класс StreamController. Он позволяет нам моделировать поток данных с помощью двух составляющих — sink (входной поток, куда пользователь добавляет события) и stream (выходной поток, который слушают один или несколько объектов и реагируют на изменения). Пример описания входа-выхода для экрана в коде Bloc-а выглядит следующим образом .
Что такое “бизнес логика”? И как начать ее понимать
Отдельные геттеры удобно делать по следующим причинам. Во-первых, так гораздо проще, чем каждый раз писать nameController.sink, во-вторых, вы даете входу/выходу осмысленное имя, что позволяет легче понять его назначение и тем самым улучшает читабельность.
Код метода build для вашего виджета может выглядеть так .
Здесь мы создаем специальный виджет StreamBuilder. Первым аргументом указываем поток, который поставляет данные нашему виджету, вторым — функцию, «собирающую» виджет на основе отрисовочного контекста и данных, асинхронно приходящих из потока.
Для реализации BLoC часто используются уже готовые библиотеки, например, эта . Нашей же команде она показалась слишком тяжеловесной, поэтому мы воспользовались кастомной, упрощенной реализацией, описанной в этой статье . Рассмотрим ее основные положения.
Знакомство с BlocProvider
Каждый Bloc поддерживает общий интерфейс BlocBase, содержащий в себе только один обязательный к реализации метод dispose. Он нужен, чтобы не забыть реализовать освобождение ресурсов, занятых Bloc-ом, например, закрыть активные потоки. Выглядит он так .
Для того чтобы вы могли получить доступ к Bloc-у из любого узла дерева виджетов, корнем этого дерева становится такая сущность, как BlocProvider. Рассмотрим ее код .
Идея заключается в том, что наш провайдер, — точно такой же виджет, который состоит из двух слагаемых: Bloc, к которому будут обращаться виджеты, и дерево виджетов, которое мы видим на экране. Для доступа к Bloc-у в методе виджета build вам нужно написать так .
Доступ к Bloc-у под капотом реализован очень просто. Мы движемся вверх по иерархии виджетов в рамках BuildContext, пока не встретим BlocProvider. Как только это произошло, мы можем вернуть Bloc этого провайдера.
Небольшое уточнение по поводу оптимизации. Поиск по иерархии виджетов имеет вычислительную сложность O (n). Если ваш провайдер расположен относительно близко к виджету, который запросил Bloc, такая операция не займет много времени. Однако в случае, когда ваш виджет имеет большую глубину вложенности, а доступ к Bloc-у ему требуется часто, это может сказаться на производительности. Тогда есть смысл подумать о том, чтобы кэшировать ссылку на Bloc, например, передавая его как аргумент в конструктор виджета.
Пример реализации
Рассмотрим, как работа с BLoC-архитектурой может выглядеть на практике. Перепишем пример, который по умолчанию генерируется при создании Flutter-проекта. Так как здесь всего один экран, у нас будет один виджет MainScreen с прилагающимся к нему MainBloc.
У MainBloc есть входной поток с UI событиями (в нашем случае нажатия на кнопку «+») и выходной поток со значением счетчика нажатий. Само значение счетчика counter из State должно переехать сюда, поскольку оно — часть бизнес-логики. В результате получаем следующий код для MainBloc .
Если создание отдельного потока для UI-событий вам кажется излишним, то вы можете написать отдельный метод-обработчик нажатия на кнопку «+» (onIncrementButton) и вызывать его напрямую.
Теперь вынесем код виджета из main.dart в MainScreen и интегрируем в него наш MainBloc .
Обратите внимание, текстовый виджет со значением счетчика теперь находится в StreamBuilder, который подписан на поток из Bloc-а. При нажатии на кнопку в Bloc прокидывается соответствующее событие (на ваше усмотрение, через поток или обработчик). Bloc обрабатывает событие, увеличивая хранящийся в нем счетчик нажатий на 1. После чего значение этого счетчика передается в другой поток, на который подписан виджет на стороне UI. StreamBuilder получает новое состояние из потока (AsyncSnapshot) и перерисовывает свое содержимое с новыми данными.
Наконец, создадим BlocProvider для нашего экрана. Вынесем этот код в main.dart .
Запускаем приложение и проверяем его работоспособность.
Источник: dzen.ru
Слой данных
О бизнес-логике, и чем она отличается от сервисов
Бизнес-логика — это то, что останется от клиентских задач, если убрать компьютеры.
Всё равно у заказа будут строки, НДС будет начисляться по тем же правилам, а кредит нельзя будет выдавать, если кредитуемый сидел в тюрьме.
Бизнес-логика выводится из use cases, а в use cases не следует упоминать компьютеры.
«Инфраструктурой» же я назваю библиотеки, которые использует само приложение. Можно взять любое другое название для layer-а, например, по фаулеровским PoEA, «сервисы», «интеграция», или «вспомогательный слой UI + доступ к данным».
Все эти вещи повторяются от приложения к приложению, с разными вариантами.
[Ядро системы 1C? Или Management Console для Windows? . задают инвариантное поведение базированных на них систем]
Это не бизнес-логика, это именно инфраструктура. Библиотека, которая сама не содержит ничего, понятного и непосредственно нужного конечному пользователю. Но которая позволяет НАД ней построить какую-то логику, и немного сэкономить на общей функциональности.
Для меня бизнес-логика — более узкое понятие. Это тот алгоритм, который нужно выполнить пользователю: дефрагментировать диск, просуммировать и проверить проводки. В этих ядра нет кода, который это делает, есть только функции, которые помогают построить GUI или DAL.
Они и есть сервисы или инфраструктура.
Если выбрать такое деление на слои, то легче будет отделить слой «пользовательских алгоритмов». И когда эти алгоритмы (бизнес-логика) поменяется, инфраструктура не будет цепляться и мешать менять логику.
Источник: singalen.livejournal.com