Авторы: Иван Моргилло, Саша Секулич и Фабрицио Чиньоли
Эта статья взята из Grokking ReactiveX.
После жизни, посвященной императивному программированию, переход к реактивному мышлению может показаться сложной задачей:
«Почему я должен отказаться от всякого контроля и склониться к тому, что« контролируют данные »?»
Давайте посмотрим на это в повседневной жизни: на систему кондиционирования воздуха. Сегодня жаркий день, и мы хотим запустить систему кондиционирования, когда температура в комнате достигнет 77F. Нам нужен градусник.
Что ж, нам нужен Thermometer
класс, который предоставляет temperature()
метод для получения текущей температуры в комнате.
Как только мы узнаем, как получить текущую температуру, мы можем объединить ее с нашим классом AirConditioning
.
Этот класс предоставляет три метода:
class AirConditioning { start() stop() isRunning() }
Чтобы изменить состояние системы кондиционирования воздуха, мы можем использовать start()
и stop()
. Если мы хотим проверить, было ли изменение статуса успешным, isRunning()
дает нам информацию о текущем статусе.
Теперь, когда мы знаем текущую температуру с помощью temperature()
и можем включить систему кондиционирования с помощью start()
, мы можем создать класс AirConManager
с базовым monitor()
методом, который будет содержать логику, необходимую для получения текущей температуры, оценки состояния и выполнения правильное действие:
fun monitor() { while (true) { if (thermometer.temperature() >= 77 && !ac.isRunning()) { ac.start() break; } } }
Текущая реализация функции monitor()
почти оскорбительна, и я пойму, если у вас начнут кровоточить глаза:
- у нас есть
while(true)
цикл, чтобы продолжать оценивать условие; - у нас есть
if
условие, которое извлекает текущую температуру, проверяет, ниже ли она 77 градусов и включена ли уже система кондиционирования воздуха; - у нас есть блок
action
, который запускает систему кондиционирования воздуха и завершает цикл.
Пока что у нас нет никаких особых требований, и нас не волнуют такие детали, как остановка системы или перезапуск цикла, если температура упадет.
Эту функцию можно реорганизовать и отшлифовать несколькими способами, но ключевым моментом все равно будет:
Постоянно проверяя температуру и решая, что делать соответственно
Как мы могли перейти на реактивный сценарий?
В сценарии, ориентированном на Rx, наш термометр был бы наблюдаемым термометром, представленным классом ObservableThermometer
.
Класс ObservableThermomenter
предоставит единственный метод: rxTemperature()
. Если мы подпишемся на этот Observable, мы будем получать текущую температуру каждый раз, когда она изменяется:
fun rxTemperature() : Observable<Int> { [...] }
Мы можем вернуться к нашему AirConManager
и провести рефакторинг для работы с новым реактивным термометром.
AirConManager
может подписаться на ObservableThermometer
в своем monitor()
методе и получить новое значение температуры:
void monitor() { rxTemperature() .subscribe(temperature -> { if (temperature >= 77 &&!ac.isRunning()) { ac.start() } }) }
Примечание. В этом .subscribe()
методе есть Subscriber
, реализующий onNext()
метод как лямбда-выражение. Subscriber
s обычно имеют три метода, но если вам не нужно получать уведомления о завершении потока или вы не хотите управлять возможными сценариями ошибок, вы можете не реализовывать onCompleted()
и onError()
.
Как только мы подпишемся, данные начнут поступать к нам: onNext()
получит значение температуры, мы оценим его и будем действовать соответствующим образом.
"Подожди секунду! Вся эта суета, чтобы сэкономить мне время? "
Один из ключевых принципов реактивного программирования заключается в том, что все дело в том, чтобы управлять данными, иметь возможность создать идеальный поток данных, который вам нужен, и работать с потоком, который доступен.
Разрешите познакомить вас с оператором .filter()
!
Push vs Pull: создайте нужный поток
В нашем сценарии нам не нужно значение температуры каждый раз, когда оно изменяется. Нам просто нужно знать, когда температура достигает 77F, чтобы запустить систему кондиционирования воздуха. Rx - это поток данных, которые мы получаем для использования, фильтрации и преобразования.
Давайте усовершенствуем наш monitor()
метод, чтобы создать нужную последовательность, отфильтровав все значения температуры, которые нам не нужны:
fun monitor() { rxTemperature() .filter(!ac.isRunning) .filter(temperature >= 77) .subscribe(temperature -> ac.start())}
Как видите, filter()
оценивает Predicate
и распространяет элемент только в том случае, если условие выполнено.
Мы должны были получать новое значение температуры в нашем onNext()
каждую секунду, но вместо этого мы хотели войти в игру, только если система кондиционирования была отключена, а температура в комнате была выше 77F. Мы использовали filter()
, чтобы отфильтровать все те сценарии, которые нас не интересовали, и получить только те значения, которые соответствуют нашему варианту использования. Мы создали идеальную последовательность для поставленной задачи из начальной общей последовательности, которую мы не можем контролировать.
В этом сила подхода Push: данные поступают к нам. Мы можем сосредоточиться на использовании данных, а не на их извлечении и управлении.
Если вы хотите узнать больше, загрузите бесплатную первую главу Grokking ReactiveX и просмотрите эту презентацию Slideshare для получения дополнительных сведений и кода скидки.
Если вам понравилась эта статья, нажмите 💚 ниже и поделитесь любовью с рецептами 😊