В предыдущей части этой статьи мы упоминали некоторые концепции, такие как декларативный стиль, побочные эффекты или функциональные принципы.

А теперь пора поговорить о стиле Reactive и о том, как мы можем совместить функциональное и реактивное программирование.

Как стать реактивным?

Представим, что мы работаем над следующей пользовательской историей:

As a Customer,
     I want to filter a set of videos by a Genre
     ...

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

Как только мы получим полный список видео, мы сможем легко их отфильтровать, выполнив следующие действия:

repository.getVideos()
          .filter(viewModel -> viewModel.getGenre().equals(genre))
          .subscribe(...);

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

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

Тогда как мы можем это сделать? Просто воспользовавшись интересным Оператором из RxJava.

Наша фоновая служба вернет Shared Observable:

return repository.getVideos().share();

Помните, что функция share () в RxJava является оболочкой от publish (). RefCount ().

Потом,

videoService.filterBy(Genre.ADVENTURE).subscribe(...);
// 5 minutes later
videoService.filterBy(Genre.DRAMA).subscribe(...);
Thread: RxIoScheduler-2 - Subscribed to retrieve Videos
Thread: main - Emit VideoViewModel{ id=2, ... genre=ADVENTURE }
Thread: main - Emit VideoViewModel{ id=3, ... genre=ADVENTURE }
Thread: main - Emit VideoViewModel{ id=9, ... genre=ADVENTURE }
Thread: main - - Fetching videos by ADVENTURE done!
Elapsed time: 5 minutes
Thread: RxIoScheduler-2 - Subscribed to retrieve Videos
Thread: main - Emit VideoViewModel{ id=6, ... genre=DRAMA }
Thread: main - - Fetching videos by DRAMA done!

Итак, реализовав метод «filterBy» в уже созданной нами службе и предоставив общий доступ к его Observable, мы можем подписаться несколько раз, а также фильтровать одни и те же полученные данные. , когда служба и сам Observable еще живы.

Теперь, в следующем спринте, мы должны применить новую функцию:

As a UI Designer,
     I want to show which videos were pinned as favourite
     ...

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

videoService.filterBy(Genre.ADVENTURE)
            // Our data flow can react on changes
            .map(DataBase::checkAndUpdateForFavourite)

Это одно из преимуществ, которое реактивный стиль программирования предлагает вам в повседневной гибкой разработке.

Я хотел бы порекомендовать хорошую статью о Реактивном программировании, написанную несколько лет назад Кевином Уэббером, вы можете найти ее по следующей ссылке:



Эта статья очень хорошо описывает принципы, охватываемые www.reactivemanifesto.org

И функциональные, и реактивные: функции первого и высшего порядка

Вы помните этот фрагмент кода?

repository.getVideos()
          .filter(viewModel -> viewModel.getGenre().equals(genre))
          .subscribe(...);

Мы неявно применили выражение фильтра, но что произойдет, если мы захотим повторно использовать один и тот же код инструкции в разных местах проекта?

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

public interface IGenre {
    Genre getGenre();
}

Изолируя конкретную функциональность с помощью контракта, мы можем создать новую функцию первого порядка, которая удовлетворяет нашему контракту и позволяет нам повторно использовать ее в любом месте. Следующая функция известна как Predicate и может применяться к фильтру RxJava.

public static <T extends IGenre> boolean byGenre(
    T object, Genre genre) {
    return object.getGenre().equals(genre);
}
repository.getVideos()
          .filter(viewModel -> byGenre(viewModel, Genre.ADVENTURE));

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

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

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

As a UX Designer,
     I want to show and hide a Loading Spinner whilst the
     application executes a long running operation
     ...

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

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

public static <T> Observable.Transformer<T, T> attach() {
  return tObservable ->
         tObservable.doOnSubscribe(LoadingSpinner.showLoading())
                    .doAfterTerminate(LoadingSpinner.hideLoading());
}

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

videoService.filterBy(Genre.ADVENTURE)
            .compose(LoadingSpinner.attach());
Thread: main - Show Loading...
Thread: RxIoScheduler-2 - Subscribed to retrieve Videos
...
...
Thread: main - Hide Loading.

По умолчанию функция высокого порядка - это функция, которая выполняет как минимум одно из следующих действий:

  • принимает одну или несколько функций в качестве аргументов
  • и / или возвращает функцию как результат

Ресурсы

Вам доступен java-проект, в котором вы можете просмотреть коды фрагментов, которые я использовал в этой статье:



Что дальше?

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

Любые отзывы, пожалуйста, напишите мне в Твиттере.