Реактивное программирование javascript. Реактивное программирование: понятие, обучение, особенности и советы специалистов

Авто 19.06.2020
Авто

Мир ООП-разработки вообще и язык Java в частности живут очень активной жизнью. Тут есть свои модные тенденции, и сегодня разберем один из главных трендов сезона - фреймворк ReactiveX. Если ты еще в стороне от этой волны - обещаю, она тебе понравится! Это точно лучше, чем джинсы с завышенной талией:).

Реактивное программирование

Как только ООП-языки доросли до массового применения, разработчики осознали, насколько иногда не хватает возможностей С-подобных языков. Поскольку написание кода в стиле функционального программирования серьезно разрушает качество ООП-кода, а значит, и поддерживаемость проекта, был придуман гибрид - реактивное программирование .

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

Хорошим примером идеи реактивного программирования может служить Excel-таблица. Если связать несколько ячеек одной формулой, результат вычисления будет меняться каждый раз, когда изменятся данные в этих ячейках. Для бухгалтерии такое динамическое изменение данных - привычное дело, но для программистов это скорее исключение.

A=3; b=4; c=a + b; F1(c); a=1; F2(c);

В этом примере функции F1 и F2 будут работать с разными значениями переменной C. Часто требуется, чтобы у обеих функций были только самые актуальные данные, - реактивное программирование позволит без изменения логики самих функций сразу же вызвать F1 с новыми параметрами. Такое построение кода дает приложению возможность моментально реагировать на любые изменения, что сделает его быстрым, гибким и отзывчивым.

ReactiveX

Воплощать с нуля идеи реактивного программирования может быть довольно хлопотно - есть подводные камни, да и времени это займет прилично. Поэтому для многих разработчиков эта парадигма оставалась только теоретическим материалом, пока не появился ReactiveX.

Фреймворк ReactiveX - это инструмент для реактивного программирования, работающий со всеми популярными ООП-языками. Сами создатели называют его мультиплатформенным API для асинхронной разработки, основанным на паттерне «Наблюдатель» (Observer).

Если термин «реактивное программирование» - это своего рода теоретическая модель, то паттерн «Наблюдатель» - готовый механизм отслеживания изменений в программе. А отслеживать их приходится довольно часто: загрузку и обновление данных, оповещения о событиях и так далее.

Паттерн «Наблюдатель» существует примерно столько же, сколько и само ООП. Объект, состояние которого может поменяться, называется издателем (популярный перевод термина Observable). Все остальные участники, которым интересны эти изменения, - подписчики (Observer, Subscriber). Для получения уведомлений подписчики регистрируются у издателя, явно указывая свой идентификатор. Издатель время от времени генерирует уведомления, которые им же рассылаются по списку зарегистрированных подписчиков.

Собственно, создатели ReactiveX не придумали ничего революционного, они просто удобно реализовали паттерн. И хотя во многих ООП-языках, и в Java в частности, есть готовые реализации паттерна, в этом фреймворке присутствует дополнительный «тюнинг», который превращает «Наблюдатель» в очень мощный инструмент.

RxAndroid

Порт библиотеки ReactiveX для мира Android называется rxAndroid и подключается, как всегда, через Gradle.

Compile "io.reactivex:rxandroid:1.1.0"

Издатель, генерирующий уведомления, здесь задается с помощью класса Observable. У издателя может быть несколько подписчиков, для их реализации воспользуемся классом Subscriber. Стандартное поведение для Observable - выпустить одно или несколько сообщений для подписчиков, а затем завершить свою работу или выдать сообщение об ошибке. В качестве сообщений могут быть как переменные, так и целые объекты.

Rx.Observable myObserv = rx.Observable.create(new rx.Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.onNext("Hello"); subscriber.onNext("world"); subscriber.onCompleted(); } });

В данном случае издатель myObserv сначала отправит строки hello и message, а затем сообщение об успешном завершении работы. Издатель может вызвать методы onNext() , onCompleted() и onEror() , поэтому у подписчиков они должны быть определены.

Subscriber mySub = new Subscriber() {... @Override public void onNext(String value) {Log.e("got data", " " + value);} };

Все готово для работы. Осталось связать объекты между собой - и «Hello, world!» в реактивном программировании готов!

MyObserv.subscribe(mySub);

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

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «сайт», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score!

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

Интерактивность понятие, которое раскрывает характер и степень взаимодействия между объектами. Используется в областях: теория информации, информатика и программирование, системы телекоммуникаций, социология, промышленный дизайн и других. В… … Википедия

Эту статью следует викифицировать. Пожалуйста, оформите её согласно правилам оформления статей. У этого термина существуют и другие значения, см. Электромаш (значения) … Википедия

зарубежные психотерапевтические техники - ГЛУБИННЫЕ ТЕХНИКИ Активная психотерапия (Фромм Райхманн). Анализ бытия (Бинсвангер). Анализ судьбы (Сонди). Анализ характера (В.Райх). Анализ Я (Х.Кохут, Э.Эриксон). Аналитическая игротерапия (М.Кляйн). Аналитическая терапия семьи (Richter).… … Большая психологическая энциклопедия

Книги

  • Реактивное программирование на С++. Проектирование параллельных и асинхронных приложений с использов , Пай Прасид, Абрахам Питер. Проектирование параллельных и асинхронных приложений с использованием библиотеки RxCpp и современного C++17 Поддерживаемые стандартом языка средства параллельного программированияСовместное…
  • , Нуркевич Т., Кристенсен Б.. В наши дни, когда программы асинхронны, а быстрая реакция - важнейшее свойство, реактивное программирование поможет писать более надежный, лучше масштабируемый и быстрее работающий код.…
  • Реактивное программирование с использованием RxJava , Нуркевич Томаш, Кристенсен Бен. В наши дни, когда программы асинхронны, а быстрая реакция - важнейшее свойство, реактивное программирование поможет писать более надежный, лучше масштабируемый и быстрее работающий код.…

Со временем языки программирования постоянно изменяются и развиваются из-за появления новых технологий, современных требований или простого желания освежить стиль написания кода. Реактивное программирование можно реализовать с помощью различных фреймворков, таких как Reactive Cocoa. Он изменяет рамки императивного стиля языка Objective-C и у такого подхода к программированию есть что предложить стандартной парадигме. Это, безусловно, и привлекает внимание iOS разработчиков.

ReactiveCocoa привносит декларативный стиль в Objective-C. Что мы подразумеваем под этим? Традиционный императивный стиль, который используют такие языки как: C, С++, Objective-C, и Java и т. д. можно описать так: Вы пишете директивы для компьютерной программы, которые должны быть выполнены определенным способом. Другими словами, вы говорите «как сделать» что-то. В то время как декларативное программирование позволяет описать поток управления как последовательность действий, «что сделать», не определяя, «как делать».

Императивное vs Функциональное программирование

Императивный подход к программированию подразумевает детальное описание каждого шага, который компьютер должен принять для выполнения задач. На самом деле, императивный стиль используют в нативных языках программирования (или используют при написании машинного кода). Это, кстати, является характерной чертой большинства языков программирования.

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

Вот основные различия языков:

1. Изменения состояния

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

Чтобы прояснить суть дела, у чистых функций есть следующие атрибуты:

  • единственный заметный вывод - возвращаемое значение
  • единственная зависимость входных параметров – аргументы
  • аргументы полностью определяются перед генерированием любого вывода

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

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

Как насчет ReactiveСocoa? Этот функциональный фреймворк для Objective-С, который является концептуально императивным языком, не включая явно чистые функции. При попытке избежать изменения состояния побочные эффекты не ограничиваются.

2. Объекты первого класса

В функциональном программировании есть объекты и функции, которые являются объектами первого класса. Что это значит? Это означает, что функции могут передаваться в качестве параметра, присваиваться переменной, возвращаться из функции. Почему это удобно? Это позволяет легко управлять блоками выполнения, создавать и объединять функции различными способами без затруднений, таких как указатели функции (char *(*(**foo)()); - развлекайтесь!).

У языков, которые используют императивный подход, есть свои собственные особенности относительно выражений первого класса. Как насчет Objective-C? У него есть блоки в качестве реализаций замыкания. Функции высшего порядка (ФВП) могут быть смоделированы путем принятия блоков в качестве параметров. В этом случае, блок является замыканием, и функция высшего порядка может быть создана из определенного набора блоков.

Однако процесс манипулирования с ФВП в функциональных языках является более быстрым способом и требует меньше строк кода.

3. Управление основным потоком

Циклы в императивном стиле представлены как вызовы функции рекурсии в функциональном программировании. Итерация в функциональных языках обычно выполняется через рекурсию. Почему? Наверное, ради сложности. Для Objective-C разработчиков, циклы кажутся гораздо более благоприятными для программиста. Рекурсии могут вызвать трудности, например, чрезмерное потребление оперативной памяти.

Но! Мы можем написать функцию без использования циклов или рекурсий. Для каждого из бесконечно возможных специализированных действий, которые могут быть применены к каждому элементу коллекции, функциональное программирование использует многоразовые итеративные функции, такие как “map ”, “fold ”, “”. Эти функции полезны для реорганизации исходного кода. Они уменьшают дублирование и не требуют записи отдельной функции. (читайте дальше, у нас есть больше информации об этом!)

4. Порядок выполнения

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

Функциональный порядок выполнения императивных выражений зависит от непостоянного состояния. Поэтому порядок выполнения имеет значение и неявно определяется организацией исходного кода. В этом вопросе, мы можем указать на различие между стратегиями оценки обоих подходов.

Отложенные вычисления или вычисления, вызванные по необходимости, на языках функционального программирования являются стратегиями. В таком случае, оценка выражения откладывается до тех пор, пока его значение не будет необходимо, там самым мы избегаем повторных оценок. Иными словами, выражения оцениваются только при оценке зависимого выражения. Порядок операций становится неопределенным.

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

5. Количество кода

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

Главные компоненты ReactiveCocoa

Функциональное программирование работает с понятиями, известными как future (представление переменной, доступное только для чтения) и promise (представление переменной, доступное только для чтения future). Что же хорошего в них? В императивном программировании Вы должны работать с уже существующими значениями, что приводит к необходимости синхронизации асинхронного кода и других трудностей. Но понятия futures и promises позволяют работать со значениями, которые еще не созданы (асинхронный код записан синхронным способом).


Сигнал

Future и promise представлены как сигналы в реактивном программировании. - основной компонент ReactiveCocoa. Он дает возможность представить поток событий которые будут представлены в будущем. Вы подписываетесь на сигнал и получаете доступ к событиям, которые произойдут со временем. Сигнал - это push-driven поток и может представлять собой нажатие кнопки, асинхронные сетевые операции, таймеры, другие события UI или что-либо еще, что изменяется в течение долгого времени. Они могут связать результаты асинхронных операций и эффективно объединить многократные источники события.

Последовательность

Другим типом потока является последовательность. В отличие от сигнала, последовательность - это pull-driven поток. Это своего рода коллекция, которая имеет аналогичное назначение, что и NSArray. RACSequence позволяет определенным операциям выполняться, когда Вы в них нуждаетесь, а не последовательно, как с коллекцией NSArray . Значения в последовательности оцениваются только когда это указано по умолчанию. Использование только части последовательности потенциально улучшает производительность. RACSequence позволяет коллекциям Cocoa обрабатываться универсальным и декларативным способом. RAC добавляет метод -rac_sequence к большинству классов коллекции Cocoa, чтобы их можно было использовать в качестве RACSequences .

Команда

В ответ на определенные действия создается RACCcommand и подписывается на сигнал. Это применяется, прежде всего, к UI взаимодействиям. Категории UIKit , предусмотренных ReactiveCocoa для большинства средств управления UIKit , дают нам корректный способ обработки событий UI. Давайте представим, что мы должны зарегистрировать пользователя в ответ на нажатие кнопки. В этом случае команда может представлять сетевой запрос. Когда начинается выполнение процесса кнопка меняет свое состояние на «неактивно» и наоборот. Что еще? Мы можем передать активный сигнал в команде (Достижимость - хороший пример). Поэтому, если сервер будет недоступен (который является нашим “включенным сигналом”), то команда будет недоступна, и каждая команда сопоставленного элемента управления будет отражать это состояние.

Примеры основных операций

Вот некоторые схемы о том, как работают основные операции с RACSignals:

Слияние/Merge

+ (RACSignal *)merge:(id)signals;


У потоков результата есть оба потока событий, объединенных вместе. Таким образом, "+ merge" является полезным, когда вы не заботитесь о конкретном источнике событий, но хотели бы обработать их в одном месте. В нашем примере stateLabel.text использует 3 различных сигнала: выполнение, завершение, ошибки.

RACCommand *loginCommand = [ initWithSignalBlock:^RACSignal *(id input) { // let"s login! }]; RACSignal *executionSignal = ; RACSignal *completionSignal = filter:^BOOL(RACEvent *event) { return event.eventType == RACEventTypeCompleted; }] map:^id(id value) { return @"Done"; }]; }]; RACSignal *errorSignal = ; RAC(self.stateLabel, text) = ];

+ (RACSignal *)combineLatest:(id)signals reduce:(id (^)())reduceBlock;

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


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

ACSignal *enabledSignal = reduce:^id (NSString *email, NSString *password) { return @( && password.length > 3); }];

* Теперь давайте немного изменим нашу команду входа в систему и подключим ее к фактическому loginButton

RACCommand *loginCommand = [ initWithEnabled:enabledSignal signalBlock:^RACSignal *(id input) { // let"s login! }]; ;

- (RACSignal *)flattenMap:(RACStream * (^)(id value))block;

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


Давайте представим, что Ваш запрос авторизаций в систему состоит из двух отдельных частей: получить данные от Facebook (идентификатор, и т.д.) и передать их на Backend. Одно из требований должно быть в состоянии отменить вход в систему. Поэтому клиентский код должен обработать состояние процесса входа в систему, чтобы иметь возможность отменить его. Это дает много шаблонного кода, особенно если Вы можете войти в систему из нескольких мест.

Как ReactiveCocoa помогает Вам? Это могло бы быть реализацией входа в систему:

- (RACSignal *)authorizeUsingFacebook { return [[ flattenMap:^RACStream *(FBSession *session) { return ; }] flattenMap:^RACStream *(NSDictionary *profile) { return ; }]; }

Legend:

+ - сигнал, который приводит к открытию FBSession . Если необходимо, то это может привести к входу в Facebook .

- - сигнал, который извлекает данные профиля через сессию, которая передается как self .

Преимущество данного подхода заключается в том, что для пользователя весь поток нечеткий, представлен единственным сигналом, который можно отменить на любой «стадии», будь то вход Facebook или вызов Backend.

Фильтр/Filter

- (RACSignal *)filter:(BOOL (^)(id value))block;

В результате поток содержит значения потока “а”, отфильтрованное согласно заданной функции.


RACSequence *sequence = @[@"Some", @"example", @"of", @"sequence"].rac_sequence; RACSequence *filteredSequence = ; }];

Map

- (RACSignal *)map:(id (^)(id value))block;

В отличие от FlattenMap, Map выполняется в синхронном режиме. Значение свойства “а” проходит через заданную функцию f (x + 1) и возвращает отображенное исходное значение.


Допустим, нужно ввести на экран заголовок модели, применяя к ней некоторые атрибуты. Map вступает в игру, когда “Применение некоторых атрибутов” описано как отдельная функция:

RAC(self.titleLabel, text) = initWithString:modelTitle attributes:attributes]; }];

Как это работаем: объеденяет self.titleLabel.text с изменениями model.title , применив пользовательские атрибуты к нему.

Zip

+ (RACSignal *)zip:(id)streams reduce:(id (^)())reduceBlock;

События потока результатов создаются, когда каждый из потоков сформировал равное количество событий. Он содержит значения, по одному от каждого из 3 объединенных потоков.


Для некоторых практических примеров, zip можно описать как dispatch_group_notify Например, у Вас есть 3 отдельных сигнала и необходимо объединить их ответы в единственной точке:

NSArray *signals = @; return ;

- (RACSignal *)throttle:(NSTimeInterval)interval;

С помощью таймера, установленного на определенный промежуток времени, первое значения потока “а” передается к потоку результата только по окончанию таймера. В случае, если новое значение производится в течение заданного временного интервала, он удерживает первое значение, не давая ему передаваться в поток результата. Вместо этого, в потоке результата появляется второе значение.


Удивительный случай: нам нужно выполнить поиск по запросу, когда пользователь изменяет searchField. Стандартная задача, да? Впрочем, она не очень эффективна для построения и отправки сетевого запроса при каждом изменении текста, поскольку textField может генерировать много таких событий в секунду, и вы придете к неэффективному использованию сети.
Выход здесь заключается в том, чтобы добавить задержку, после которой мы на самом деле выполним сетевой запрос. Обычно это достигается добавлением NSTimer. С ReactiveCocoa это гораздо проще!

[[ throttle:0.3] subscribeNext:^(NSString *text) { // perform network request }];

*Важным замечанием здесь является то, что все «предыдущие» textField изменяются до того, как «последние» будут удалены.

Задержки/Delay

- (RACSignal *)delay:(NSTimeInterval)interval;

Значение, полученное в потоке “а” задерживается и передается в поток результата через определенный интервал времени.


Как аналог -, delay только задержит отправку “следующих” и“завершенных” событий.

[ subscribeNext:^(NSString *text) { }];

Что нам нравится в Reactive Cocoa

  • Знакомит Cocoa Bindings с iOS
  • Возможность создавать операции по будущим данным. Вот немного теории о futures & promises от Scala.
  • Возможность представлять асинхронные операции синхронным способом. Reactive Cocoa упрощает асинхронное программное обеспечение, например сетевой код.
  • Удобная декомпозиция. Код, который связанный с пользовательскими событиями и изменениями состояния приложения, может стать очень сложным и запутанным. Reactive Cocoa делает модели зависимых операций особенно простыми. Когда мы представляем операции в виде объединенных потоков (например, обработка сетевых запросов, пользовательские события, и т.д.), мы можем достигнуть высокой модульности и свободной связи, что приводит к более частому использованию кода повторно.
  • Поведения и отношения между свойствами определены как декларативные.
  • Решает проблемы с синхронизацией - если Вы объединяете несколько сигналов, тогда есть одно единое место для обработки всех результатов (будь то следующее значение, сигнал завершения или ошибки)

С помощью фреймворка RAC Вы можете создавать и преобразовывать последовательности значений в лучший, более высокого уровня, способ. RAC позволяет проще управлять всем тем, что ожидает завершения асинхронной операции: отклика сети, изменения зависимого значения и последующей реакции. На первый взгляд с ним трудно иметь дело, но ReactiveCocoa заразительный!



Рекомендуем почитать

Наверх