Авторы: Иван Моргилло, Саша Секулич и Фабрицио Чиньоли

Эта статья взята из 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() метод как лямбда-выражение. Subscribers обычно имеют три метода, но если вам не нужно получать уведомления о завершении потока или вы не хотите управлять возможными сценариями ошибок, вы можете не реализовывать 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 для получения дополнительных сведений и кода скидки.

Если вам понравилась эта статья, нажмите 💚 ниже и поделитесь любовью с рецептами 😊