![](https://habrastorage.org/webt/ez/hr/x0/ezhrx0ymj-ak_tdpfqhioagb3su.jpeg)
Эта статья рассчитана на широкий круг читателей, которые хотели бы узнать, что такое Реактивное Программирование. Цель этой статьи — сформировать у вас базовые Ментальные Модели Реактивного Программирования (ММ РП), не вдаваясь в технические детали.
Disclaimer
РП (Реактивное Программирование) — тема столь же многообразная, сколь и холиварная. Берясь за освещение этой темы, нельзя не вызвать на себя огонь скрытой или открытой критики со стороны глубоких профессионалов и просто религиозно преданных этой области фундаменталистов.
Поэтому хочу предупредить тех, кто решит читать эту статью дальше: я предлагаю в ней читателям познакомиться с моими Ментальными Моделями Реактивного Программирования, сформировавшимися на основании моего личного опыта изучения и использования РП. Возможно, это предохранит некоторых читателей от ошибок, совершённых мной при освоении РП.
Статья не претендует на ширину охвата и глубину проработки этой темы.
Поэтому хочу предупредить тех, кто решит читать эту статью дальше: я предлагаю в ней читателям познакомиться с моими Ментальными Моделями Реактивного Программирования, сформировавшимися на основании моего личного опыта изучения и использования РП. Возможно, это предохранит некоторых читателей от ошибок, совершённых мной при освоении РП.
Статья не претендует на ширину охвата и глубину проработки этой темы.
Но сначала объясним, при чём здесь ментальные модели и начальники, упомянутые в заголовке статьи…
О Ментальных Моделях
По моему мнению, перед тем, как начать пытаться применять реактивное программирование в своем проекте, вам необходимо научиться мыслить понятиями реактивного программирования. С этим надо разобраться прежде, чем вы попытаетесь понять соответствующие технические аспекты.
На основании многолетнего опыта занятия самыми разными разделами Программирования я пришёл к выводу, что создание программ и систем есть не что иное, как Материализация Идей (см. [1], [2])
Что подразумевается под этим термином? Как правило, процесс начинается с того, что сначала в голове заказчика или самого исполнителя появляются смутные идеи или представления о бушующем продукте или изменении существующего. Будем называть их Ментальными Моделями (ММ), отдавая себе отчет, что мы сами не очень представляем, что это такое. Когда участники проекта общаются между собой, создают разного рода документы, то Ментальные Модели «кристаллизуются», «выпадая» в конце концов в «сухой осадок» в виде программного кода.
Чуть ниже я попытаюсь показать, что ментальные модели, необходимые для успешного применения Реактивного Программирования (РП), радикально отличаются от традиционных моделей, необходимых для программирования в рамках Объектно-Ориентированного Программирования (ООП).
На основании многолетнего опыта занятия самыми разными разделами Программирования я пришёл к выводу, что создание программ и систем есть не что иное, как Материализация Идей (см. [1], [2])
Что подразумевается под этим термином? Как правило, процесс начинается с того, что сначала в голове заказчика или самого исполнителя появляются смутные идеи или представления о бушующем продукте или изменении существующего. Будем называть их Ментальными Моделями (ММ), отдавая себе отчет, что мы сами не очень представляем, что это такое. Когда участники проекта общаются между собой, создают разного рода документы, то Ментальные Модели «кристаллизуются», «выпадая» в конце концов в «сухой осадок» в виде программного кода.
Чуть ниже я попытаюсь показать, что ментальные модели, необходимые для успешного применения Реактивного Программирования (РП), радикально отличаются от традиционных моделей, необходимых для программирования в рамках Объектно-Ориентированного Программирования (ООП).
Причём здесь начальники...
Наверное все дети проходят в своём развитии период словотворчества. Однажды их мозг становится способным создавать формально правильные конструкции языка типа «песковатор» или «режик» и их приходится огорчать сообщением, что взрослые для обозначения этих предметов уже придумали до них другие слова: экскаватор и ножик. (Возможно вы замечали, что «словотворчество» характерно и для взрослых, когда они говорят на неродном языке).
Однажды мой сын, пребывавший на «словотворческом» этапе развития, вернулся с прогулки с сообщением, что познакомился во дворе со сверстником, который тоже, как и он сам, начальник. Выяснилось, что его новый друг тоже только начал кататься на велосипеде. От слова «начал» и было создано в детской голове слово «начальник». Важным оттенком нового понятия было то, что начальникам, в силу их статуса, можно простить незнание некоторых важных моментов и определенное неумение.
Размышляя над планом этой статьи, я вспомнил эту историю, поскольку хотелось обратиться не только к начинающим в области РП программистам, тестерам и т.д., но также и к их руководителям (начальникам) — архитекторам, руководителям и проекта, как впрочем и остальным участникам процесса создания и поддержки программ и систем.
Будем надеяться, что употребление этого слова в заголовке статьи себя оправдало и привлекло ожидаемую категорию читателей.
Однажды мой сын, пребывавший на «словотворческом» этапе развития, вернулся с прогулки с сообщением, что познакомился во дворе со сверстником, который тоже, как и он сам, начальник. Выяснилось, что его новый друг тоже только начал кататься на велосипеде. От слова «начал» и было создано в детской голове слово «начальник». Важным оттенком нового понятия было то, что начальникам, в силу их статуса, можно простить незнание некоторых важных моментов и определенное неумение.
Размышляя над планом этой статьи, я вспомнил эту историю, поскольку хотелось обратиться не только к начинающим в области РП программистам, тестерам и т.д., но также и к их руководителям (начальникам) — архитекторам, руководителям и проекта, как впрочем и остальным участникам процесса создания и поддержки программ и систем.
Будем надеяться, что употребление этого слова в заголовке статьи себя оправдало и привлекло ожидаемую категорию читателей.
Зачем Реактивное Программирование нужно вашему проекту?
Многие люди, незнакомые с РП, относятся изначально к нему скептически, подозревая, что за этим скрывается очередная пустая мода, прикрытая парой красивых слов. Особенно, когда они узнают, что оценить РП можно, только попробовав его. А попробовать его дорого, из-за высокого порога вхождения. Жили мы себе и жили с ООП, чего в нём не хватает?
Позвольте представить мою точку зрения на этот счёт.
На заре Программирования, когда большинство программ писалось прямо на ассемблере, основным рабочим понятием (элементом Ментальной Модели) программистов была инструкция, или команда языка. Какие-то (примитивные) данные подаются на вход команды или инструкции. Инструкция отрабатывает и выдаёт какие-то данные на выход. Появление первых процедурных языков программирования типа Фортрана суть дела не изменили. Просто данные и выполняемые операции (как последовательность элементарных команд) стали сложнее.
Со временем стало ясно, что эта концепция не очень соответствует реалиям мира. Данных может быть много, они могут быть сложно структурированы. И данные, и функциональность вокруг них хорошо бы разбить на части, чтобы разрабатывать и поддерживать раздельно, а использовать вместе.
Эти проблемы во многом решил ООП. Единицей Ментальной Модели типичного программиста ООП стал объект со спрятанными (инкапсулированными) в нём данными и интерфейсом доступа к этим данным в виде набора функций.
ООП сыграл огромную роль в автоматизации и компьютеризации многих производственных и прочих процессов. И вместе с этим обнажились его слабости.
К сожалению, в ООП отсутствует понятие процесса как такового.
Улучшить ситуацию пытались разными способами, концентрируясь на различных аспектах.
Так появились на свет Event-Driven Programming [3], Dataflow-programming [4], Stream processing [5] и ряд других парадигм.
Рискну вызвать на себя поток критики приверженцев и знатоков этих парадигм, попытавшись простыми словами передать их общую суть.
Эти парадигмы так или иначе оперируют с потоками информации. При этом Event-Driven Programming, как и подсказывает само название, делает упор на процессе возникновения информационных элементов потока, Dataflow programming — на управлении потоками (расщепление, слияние, трансформация потоков) а Stream processing на оптимальное использовании ресурсов при обработке потоков.
Реактивное Программирование — это примерно о том же, но с фокусом на конкретные элементарные операции создания, управления и использования потоков. Т.е. РП описывает, как ваша система реагирует (англ. react) на элементы информационного потока. В этом смысле было бы правильнее в русском языке для его обозначения использовать термин «Реагирное Программирование» (от слова «реагировать») или «Реакционное Программирование» (от слова «реакция на что-то»), если бы первый вариант не резал ухо, а второй не вызывал неверных ассоциаций.
Рискну высказать ещё одну крамольную мысль. То, что мы сегодня называется в английском языке Реактивным Программированием (Reactive Programming). называется так в силу исторических причин и склонившегося в пользу этого термина мнения большинства.
Эта парадигма вполне могла бы называться по-другому. Поэтому не фокусируйтесь на её нынешнем названии, а постарайтесь понять её суть.
И хотя я буду говорить о РП на достаточно абстрактном уровне, в качестве конкретных примеров я буду приводить API библиотеки RxJS.
Акроним RxJS означает Reactive Extension for JavaScript — расширение JavaScript функциями Реактивного Программирования. Схожие расширения существуют и для многих других языков программирования, как это можно видеть на картинке внизу, взятой из [6].
![Reactive programing extensions](https://habrastorage.org/webt/ii/gb/ji/iigbjinggcawbioey0iz7ztarzs.png)
Зачем Ментальные Модели РП нужны вашему проекту
Большие проекты не делаются в одиночку. Нередко можно читать или слышать, что участники проекта должны говорить на одном языке. Мой опыт показывает, что такое вряд ли нужно и возможно. А вот что нужно — чтобы самые базовые понятия проекта излагались и понимались участниками проекта по возможности одинаково. В терминах Ментальных Моделей (ММ) можно сказать, что верхнеуровневые ММ должны быть максимально похожими.
Но как они могут быть похожими, если аналитики мыслят категориями Workflow и Use Cases, архитекторы паттернами, разработчики функциями и структурами данных, а тестеры — тестовыми сценариями?
Я не призываю всех этих специалистов одновременно начать мыслить категориями Реактивного Программирования, но могу им пообещать, что знакомство с этими категориями упростит и повысит эффективность их профессионального общения с коллегами. Это должно произойти потому, что с одной стороны, ММ РП обладают мощью, необходимой для описания сложных Workflow, а с другой стороны, ММ РП могут быть практически напрямую обращены в код на многих языках программирования.
Неожиданности-опасности, или что в РП не так, как мы все привыкли
Но перед тем, как мы перейдём описанию того, из чего состоят Ментальные Модели Реактивного Программирования, исходя из собственного опыта, я хотел бы предупредить читателя о том, чего в них нет. Более того, не просто нет, а и самого ожидание простого и понятного в мире ООП поведения приводит к печальным последствиям.
Делаю это отнюдь не с целью запугать, а скорее — с целью заинтриговать читателя.
Отличие 1: Вместо модели курсора — вычислительный граф
Рискну предположить, что у многих читателей при продумывании очередной подлежащей реализации задачи в голове возникает ментальная модель, которую я называю моделью курсора. В ней предполагается, что для решением поставленной проблемы будет придуман пошаговый алгоритм в виде линейного списка инструкций. Выполнение алгоритма сводится к пошаговому исполнению инструкции одна за другой. Можно себе вообразить нечто вроде указателя на выполняемую в данный момент инструкцию. После выполнения инструкции указатель (курсор) перемещается к следующей инструкции в списке и она начинает исполняться.
В рамках этой модели записанная на условном объектно-ориентированном языке программирования последовательность команд
1. Х1 = 2
2. Х2 = 3
3. Х3 = Х1 + Х2
4. Напечатать Х1, Х2, Х3
5. Х1 = 4
6. Напечатать Х1, Х2, Х3
выдаст результат
2 3 5
4 3 5
Наша курсорная ментальная модель прекрасно предсказывает и обьясняет такой результат. После обработки третьей строчки значение Х3 задано и новое значение для Х1, заданное в строчке 5, не может его изменить.
В мире РП, в зависимости от интерпретации операции «+», результат будет скорее всего таким
2 3 5
4 3 7
В этом мире большинство операций связывают между собой входные параметры, создавая тем самым вычислительные графы, через которые «проталкиваются» вычисления при изменении одного или нескольких параметров.
Отличие 2: Асинхронность выполнения операций
В рамках курсорной ментальной модели вычислений следующая операция не может начаться раньше, чем закончится предыдущая.
Рассмотрим следующий пример. Предположим, что функция f1 начисляет базовую зарплату по значению пользовательского идентификатора userId, а функция f2 — начисляет бонус на основании userId и значения зарплаты.
Тогда вычисление полной зарплаты может выглядеть так
1. X = f1(userId)
2. Y = f2(userId, X)
Напечатать X, Y
Предположим, что некоторому сотруднику положена базовая зарплата 10000 ед. и бонус 1000 ед.
Наша курсорная ментальная модель подсказывает, что распечатается
10000 1000
Увы, в мире асинхронного РП результат может, в зависимости от продолжительности операций быть
0 0
10000 0
0 1000
10000 1000
(возникновение исключений я пока не рассматриваю).
Всё дело в том, что в асинхронно-реактивном мире следующая операция не ждёт окончания предыдущей, если она предыдущая) асинхронная.
Чтобы представить это наглядно, давайте рассмотрим некоторые важные детали на реалистичном примере, показанном на рисунке внизу.
На картинке показано время исполнения четырёх независимых друг от друга инструкций L1, L2, L3 и L4 (нам важны их номера, а не написание) в синхронном (верхняя часть рисунка) и асинхронном (нижняя часть рисунка) режимах.
![](https://habrastorage.org/webt/vb/s5/rb/vbs5rbmbwmrmmhqtw7gtx9tivzc.png)
Как мы видим, в первом случае каждая последующая инструкция «ждёт» окончания предыдущей. В асинхронном случае все инструкции начинают выполняться одновременно. Из-за параллельного исполнения и использования ресурсов большинство инструкций в асинхронном режиме выполняется дольше, чем в синхронном. Однако все вместе они завещают свою работу раньше.
Порядок окончания выполнения инструкций в обоих режимах тоже очень разный. В синхронном это:
L1, L2, L3, l4
а в асинхронном: L3, L2, L1, L4
. Отличие 3: Неполная цепочки (без потребителя) не работают вообще
Во многих традиционных языках программирования принято связывать вызовы функций или свойств объектов с помощью точек.
Например, следующая цепочка вызовов функций JavaScript превращает слово «good“ в „dog“:
„good“.split(„“).reverse().join(„“).replace(„oo“, „o“);
Последовательности (цепочки) могут быть длинными. Их можно из соображений повторного использования или удобства разрезать на части и выполнять частично.
Разбиение цепочки в РП на две части и вызов только одной из них как правило приводит к отсутствию результата, поскольку только полная цепочка (с потребителем на конце) выполняется.
Зачем всё это?
Наверное, многие читатели уже сейчас задают себе вопрос «А не сошли ли они коллективно с ума, эти реактивные программисты? Зачем оно нужно, такое программирование?»
Не берусь предугадывать, что ответили ли бы на этот вопрос создатели и эксперты РП, но мой ответ такой: такое программирование нужно, потому что многие объекты реального мира ведут себя именно так.
Вычислительные графы — именно на этом построен Excel, от которого в восторге не только бухгалтеры, но и менеджеры проектов.
Асинхронность операций. Когда вы у себя дома на кухне готовите кофе или завариваете чай — вы всё это время стоите на кухне и смотрите на ваш кофейник или чайник? Нет. Прибор кипятит воду и делает своё дело, а вы делаете пока что-то другое.
Полная цепь операций. Попробуйте вытащить вилку вашей настольной лампы из розетки и понажимать на выключатель. Лампа от этого не загорается. Этот объект работает только при наличии полной цепочки — от источника до потребителя электроэнергии. И таких объектов в реальном мире много, если не большинство.
Хочу вас успокоить, ваши познания традиционного программирования и курсорную ММ не стоит выбрасывать на помойку из-за появления РП. Реактивное программирование оставило их в покое и расширило новыми операциями над новыми типами объектов. Как — об этом мы поговорим далее.
Пространство Ментальных Моделей Программирование и место ММ РП в нём
Говоря о месте РП в общем ландшафте Программирования, авторы часто упоминают две размерности — по сложности обрабатываемых объектов и по синхронности/асинхронности операций. Пример такой классификации вы можете найти в книге „RxJS in Action“ [7], в главе «When and where to use RxJS».
В этой классификации размерность объектов подразделяется на одиночные объекты и мульти-объекты: массивы, списки и т.д. Операции подразделяются на синхронные и асинхронные.
Таким образом эта классификация разбивает мир программирования на четыре области. РП является одной из этих областей, ответственной за обработку мульти-обьектов асинхронными операциями.
Я нахожу такую классификацию весьма интересной, но хотел бы взглянуть на неё с точки зрения ментальных моделей. Таблица внизу представляет их.
Одиночные значения и объекты | Массивы, кортежи, последовательности значений или объектов | |
---|---|---|
Синхронные операции | Инструкции и курсор | Цикл, струя (Stream) |
Асинхронные операции | Семафор, ожидание (Promise) | Поток (Workflow) |
Будем считать, что ментальные модели инструкций и курсора дальнейших разъяснений не требуют.
Цикл является расширением ММ инструкций и курсора за счёт дополнительной инструкции цикла или возврата в некоторую точку. Это позволяет набор инструкций обработки одиночного объекта «обернуть» в цикл, и тем самым обработать много таких объектов. При этом курсор движется внутри цикла как и предыдущей модели, а достигнув точки перехода, либо прыгает к началу либо обработка цикла прекращается, если все объекты обработаны.
Струя. Отличие этой ментальной модели от предыдущей заключается в том, что курсор,
указывающий на обрабатываемый объект, остаётся на месте, а объекты сами «наезжают» на него.
Рассмотрим это на двух примерах. Если вы красите деревянный забор, вы по аналогии с моделью курсора переходите от доски к доске. А вот рабочий на конвейере остаётся на месте и по аналогии с моделью струи подлежащие обработке детали сами приближаются к нему. Такого рода объекты часто обозначают термином английским Stream, например в языке Java.
Семафор. Эту ММ проще всего ассоциировать со светофором на перекрёстке. Асинхронные объекты периодически опрашивают состояние некоторой общедоступной переменной и производят те или иные действия после изменения её состояния. (как водители перед светофором)
Ожидание. Подходящая метафора для данной Ментальной Модели Ожидания — письмо на бумаге или Emall, которое вы ожидали при вашем последнем устройстве на работу. Там может стоять положительный или отрицательный ответ. Ваше поведения после получения письма очень сильно зависит от его содержания. Для описания такого рода объектов часто используется английский термин Promise. То, что с точки зрения пользователя является ожиданием, для исполнителя, предоставляющего услуги, является скорее обещанием.
Как мы видим из описания, движение вдоль каждой размерности (сверху вниз или справа налево в таблице) означает качественное изменение в Ментальной Модели.
Как видно из таблицы, струи и ожидания являются соседями слева и сверху интересующей нас юго-восточной клетки таблицы. Что нового содержат Ментальные Модели Потоков, населяющих интересующую нас клетку по сравнению с ними?
В чём заключается расширение?
Расширением Потоков по сравнению с Ожиданиями является то, что ожидаемая информация может поступать не единоразово, а многими порциями. При этом процесс может оборваться не закончившись. Т.е. после ряда успешных порций мы получим уведомление об ошибке. Кроме того, добавляется ещё один вариант информации — уведомление об окончании процесса.
Это означает например, что возможно получение нескольких (но не всех) порций ожидаемой информации и (без сообщения об ошибке) сообщение об окончании обработки.
Напомним ещё раз, у Ожидания мы имеем только два альтернативных варианта результирующей информации.
Ментальная Модель Струи хорошо подходит для осмысливания, обсуждения и реализации процесса трансформации последовательности однотипных объектов. ММ Потока расширяет её следующими аспектами:
- Струй может быть много и мы можем их сливать воедино
- Струи могут быть неоднородны
- Мы можем расщеплять струи на новые по разным критериям
- Мы можем в рамках одного потока «закрывать» и/или преобразовывать их в новые.
Итак, мы определили место ММ РП (Потоков) в пространстве или ландшафте объектов Программирования. Давайте теперь опустимся с высоты птичьего полета пониже и рассмотрим Потоки и их Ментальные Модели поближе.
Потоки и фазы их жизненного цикла
В качестве первого приближения Потоки РП можно мысленно представлять себе как потоки воды в трубах водопровода или потоки электричества. При этом необходимо помнить, что как любая другая аналогия, эта аналогия имеет свои границы а применима не всегда.
Говоря о потоке, можно выделить следующие важные аспекты:
- Каждый поток как-то возникает
- Он как-то движется к потребителю
- По пути с ним что-то происходит (он трансформируется)
- Он может быть расщеплён на несколько потоков или слит с другими потоками
- Потребитель как-то использует поток, прекратив его существование.
Перечисленные аспекты одновременно являются фазами жизненного цикла отдельных элементов потока.
Рассмотрим их поподробнее на примере функций RxJS.
Создание потоков
Потоки могут быть созданы из пассивных элементов типа массива или списка объектов в вашей программе, байтов, строк файла и т.д. Такого рода источники потоков называют холодными (хотя технически существует более точное определение холодных источников потока).
Так называемые горячие источники «живут своей жизнью» и если к ним вовремя не подключиться, информация будет потеряна. К этой категории относится информация о действиях пользователя за компьютером, таблетом, смартфоном, например — информация о нажатиях на клавиши, движениях мыши или прикосновениях к экрану. Также к этой категории относятся данные запрашиваемые по различным протоколам типа HTTP, данные различных сенсоров.
Следует отметить, что существуют так называемые «теплые» источники. Кроме того, некоторые «горячие» источники можно «остудить», а «холодные» «разогреть». Но об этом вам следует почитать в специальной литературе, например в книге [7].
Для нас важно знать, что все операции создания потоков создают объекты одного типа, которые можно в дальнейшем обрабатывать одними и теми же операциями, независимо от содержания. В этой статье мы называем эти объекты потоками. Соответствующее английское название — Observable.
Движение к потребителю и трансформирование потоков
Операции трансформации потока могут производиться как на пути к потребителю, так и им самим. В обоих случаях операции обработки элементов потока строго последовательны, т.е. следующая операция запускается строго только после того, как предыдущая отработает и передаст ей свой результат.
В отличии от Stream, которые в некоторых языках программирования являются конструкциями языка со своими синтаксисом и семантикой, реактивные расширения типа RxJS в JavaScript вынуждены использовать синтаксис и базовую семантику расширяемого языка. Поэтому в RxJs реализована функция pipe(), внутри которой можно расположить вызовы функций — обработчиков как самого потока, так и его отдельных элементов.
Важно отметить, что обработчиками потока могут быть только специальные, pipeable, функции.
«Трёхфазный поток»
Если продолжать аналогию с электричеством, то рассматриваемые нами потоки можно назвать трёхфазными. Наряду с «нормальным проводом», передающим основную информацию, существует также «провод ошибок» и «провод окончания потока». Операции трансформации позволяют не только изменить объект, но и перенаправить его в другой «провод». Этим приёмом пользуются например, при обработке предполагаемых ошибок в работе с серверами по HTTP протоколу. Например, если не отвечает один сервер, можно попробовать запросить другой без информирования пользователя об неудаче с запросом первого сервера.
Это ещё очень важный элемент вашей Ментальной Модели потоков. Если в традиционных парадигмах программирования ошибка возвращается из обрабатывающей функции как код ошибки либо должен быть перехвачен как прерывание (exception), то в потоках ошибка «течёт» по независимому от основного каналу.
Там она может быть обработана. Например, если пользователь ввёл неправильный пароль, ему может быть дана дополнительная возможность попробовать ввести его ещё один или несколько раз.
Расщепление и слияние потоков
Расщепление потоков осуществляется в два этапа. На первом этапе заводятся пустые потоки. Затем, на втором этапе (этапе обработки потока) в одной из функций обработки элементы будут анализироваться и перенаправляться в нужный поток. Технически существует много вариантов, как это сделать. Например, убирая его из текущего потока или клонируя перед запуском в новый поток.
Сливать несколько потоков в один можно удивительно большим количеством способов.
Самые простые приходящие на ум способы — сливать их в порядке поступления или сначала все из первого потока а потом все из второго.
Показанный внизу на картинке метод позволяет из двух потоков образовывать один, содержащий упорядоченные пары из объектов первого и второго потоков. При этом новая пара образуется, если в одном из потоков появляется новый элемент. А содержит пара строго последние элементы каждого потока. Это приводит к тому, что один и тот же элемент может быть включен в несколько пар.
Употреблённая в этом примере графическая нотация называется Marble — диаграммы и очень эффективна при объяснении семантики операций расщепления и слияния потоков.
Если вас заинтересовала именно эта тема, советую поизучать операции и их Marble — диаграммы на ресурсе [8].
![](https://habrastorage.org/webt/cw/q7/a1/cwq7a1t6omorptqk33hgm93yetw.png)
Использование потоков
Для того, чтобы использовать элементы потока, пользователь или клиент должен на него сначала подписаться. Как правило, в конце обработки он должен от него отписаться, поскольку сборщики мусора не всегда могут автоматически деактивировать подписку при попытке утилизации подписчика.
На один поток могут подписаться много клиентов. В RxJs функция подписки называется subscribe(). В ней в большинстве случаев целесообразно разместить вызовы обработки «нормальных» элементов потока, обработчик ошибок и (относительно редко) обработчик окончания потока.
Каждый из подписчиков потока получает свою копию элемента или клон оригинального элемента. Чтобы не наделать проблем, поток реализуется так, что получаемые на обработку элементы становятся неизменяемыми. В некоторых ситуациях это ограничение всё же можно обойти, но лучше этого не делать.
Опасное обаяние потоков
Потоки очень непростые объекты, чем-то сродни интегралам в математике. Одно дело знать, что они существуют и даже примерно представлять, что это такое, и совсем другое — уметь ими пользоваться.
Понимание внутренней логики их функционирования, необходимое, чтобы хорошо применять на практике, требует немалых интеллектуальных усилий.
Потоки по своей сути тесно связаны с функциональным программированием. Для грамотного применения потоков полезно понять, как можно строить и применять функции второго порядка — функции, у которых аргументами служат другие функции.
Тогда красота и элегантность потоков откроются вам в полной мере.
Потоки заразительны. После постижения их красоты хочется применять их во всех задачах, чего разумеется делать не надо.
В каких задачах целесообразно применять потоки, а где следует использовать традиционные методы, каждый решает сам.
Подведём итог
В этой статье я попытался рассказать вам о Ментальных Моделях Реактивного Программирования (ММ РП) и даже частично заложить их в ваше Сознание. Повторим ещё раз основные тезисы.
- ММ РП особенные, не похожие на Ментальные Модели традиционного Программирования.
- Приступая к Реактивному Программированию, надо помнить, что некоторые хорошо зарекомендовавшие себя в других областях ММ типа курсора, цепочки вызовов либо цикла не работают, или работают не так.
- Основной Ментальной Моделью РП является «трехканальный поток» с каналом для «нормальных» элементов, ошибок и информацией об окончании потока.
- Потоки могут быть конечные и бесконечные.
- Потоки создаются из разнообразных «холодных», «горячих» и «тёплых» источников. Источники можно «охлаждать» и «подогревать».
- Потоки можно по пути к потребителю изменять. При этом некоторые операции изменяют сам поток (например, отфильтровывая его элементы или укорачивая сам поток). Другие операции могут изменять сами элементы и перенаправлять их из канала в канал.
- Потоки можно расщеплять и сливать вместе, одновременно меняя их содержимое.
- Для использования элементов потока на него надо подписаться, а потом отписаться.
- На один поток может быть подписано много клиентов. Каждый из них получает свой клон обрабатываемого элемента.
- Если у потока нет ни одного подписчика, поток «стоит».
Если вы заинтересовались этой темой, «поиграть» с потоками можно с помощью симуляторов, доступных на сайте [8].
Если вы хотите поглубже понять концепции РП, рекомендую проработать книгу [7], и конечно, познакомиться с The Reactive Manifesto [11].
Следующего уровня в формировании собственных ММ РП вы достигните, проработав книги [9] и [10], посвящённые проектированию и моделированию реактивных систем.
Литература и ссылки
- Программирование — это материализация идей. (Статья на Хабре. habr.com/ru/post/425321)
- Sirotin V. RPSE: Reification as Paradigm of Software Engineering. arxiv.org/abs/1810.01904
- Event-driven programming. en.m.wikipedia.org/wiki/Event-driven_programming
- Dataflow-programming. en.m.wikipedia.org/wiki/Dataflow_programming
- Stream-processing. en.m.wikipedia.org/wiki/Stream_processing
- Rx-Extensions: reactivex.io/languages.html
- RxJS in Action. – 4. August 2017. Paul P. Daniels (Autor), Luis Atencio. Manning Publications. ISBN-13: 978-1617293412
- RxJS online Documentstion. xgrommx.github.io/rx-book/index.html
- Reactive Design Patterns. 2017. Roland Kuhn Dr., Brian Hanafee, Jamie Allen. Manning Publications. ISBN-13: 978-1617291807
- Functional and Reactive Domain Modeling. 2016. Debasish Ghosh.Manning Publications. ISBN-13: 978-1617292248
- The Reactive Manifesto www.reactivemanifesto.org
Титульная иллюстрация: geralt