Мир ООП-разработки вообще и язык 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).

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

Рис. 1. Паттерн «Наблюдатель»
Рис. 1. Паттерн «Наблюдатель»

Паттерн «Наблюдатель» существует примерно столько же, сколько и само ООП. Объект, состояние которого может поменяться, называется издателем (популярный перевод термина 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<Object>() {
        @Override
        public void call(Subscriber<? super Object> subscriber) {
            subscriber.onNext("Hello");
            subscriber.onNext("world");
            subscriber.onCompleted();
        }
    }
);

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

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

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

myObserv.subscribe(mySub);

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

 

MVP

Академические примеры работы ReactiveX ты найдешь на официальном сайте фреймворка, а в этой статье мы с тобой разберем более приземленный пример.

Как показывает практика, создавая что-то сложнее студенческого курсовика, следует строить приложение с использованием еще одного паттерна — MVP (Model, View, Presenter). Он позволяет разбить ООП-проект на отдельные, слабо связанные и легко заменяемые логические блоки кода.

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

  • Model — поставщик данных в приложении. Внутри него аккумулируется вся механика запросов: сетевое взаимодействие, работа с файлами и прочее.
  • View отвечает за UI и все, что рядом. В этом блоке генерируются запросы на выдачу необходимых данных и, при желании, ведется их финальная обработка: сортировка, выборка отдельных значений и так далее.
  • Presenter действует как посредник. Поскольку View и Model мало что знают друг и о друге, этот блок служит своего рода «переходником», перенаправляя запросы от одного к другому.
Рис. 2. Паттерн MVP
Рис. 2. Паттерн MVP

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

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

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

public class Model {
    НeavyOperation heavyOp;
    public Model(String baseUrl)
    {
        ...

Источников, генерирующих данные, наверняка окажется несколько, поэтому внутри Model будет объект класса HeavyOperation.

heavyOp = new HeavyOperation();

Как понятно из названия, HeavyOperation будет работать очень долго, а в Android длительные операции нужно всегда выполнять в отдельном потоке.

private UserList prepareList()
{
    UserList repoList = new UserList();
    Thread.sleep(10000);
    repoList.addItem(new UserItem("jack"));
    addItem(new UserItem("bob"));
    ...
    return repoList;
}

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

Используем фреймворк, чтобы выполнить все эти вычисления где-нибудь в фоне, а результат вернуть обратно — в главный поток. Издатель выполнит у себя метод prepareList, а результат, объект класса UserList, уйдет подписчикам.

Observable<UserList> observableHeavyOp;
...
public Observable<UserList> doTheJob(){
    observableHeavyOp = Observable.create(new Observable.OnSubscribe<UserList>() {
    @Override
    public void call(Subscriber<? super UserList> subscriber)
        {subscriber.onNext(prepareList());}
    });
}

Многопоточность в ReactiveX создается просто: указываем, что вычисления нужно выполнять в отдельном потоке (subscribeOn), а результат вернуть в главный поток (observeOn).

observableHeavyOp.subscribeOn(Schedulers.newThread())
    .observeOn(AndroidSchedulers.mainThread());

Блок Model должен не только создать объект на основе класса HeavyOperation, но и реализовать get-метод, дающий доступ к нему извне.

public HeavyOperation getAPI()
    {... return heavyOp;}

Теперь перейдем к Presenter, связующему блоку. Здесь необходимо сохранить ссылки на блоки Model и View, а также связать их между собой get-методами.

public PresenterSimple(MainActivity v, Model model)
{
    this.view=v;
    this.model = model;
}

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

public void loadDatawithRx(String url) {
    view.inProgress(true);

В Presenter нужно прошить из Model ссылку на объект издателя, для этих целей и были созданы get-методы.

Observable<UserList> myObservable = model.getAPI().doTheJob();

Издатель уже есть, осталось сгенерировать подписчика. Создадим его тут же, используя метод subscribe и класс Observer.

myObservable.subscribe(new Observer<UserList>() {
    ...
    public void onNext(UserList repoList) {
        view.inProgress(false);
        view.showResult(repoList);
        ...
    }
});

И вот сила ReactiveX в действии — нам удалось связать паттерном «Наблюдатель» блоки Model и View. Метод doTheJob сгенерирует новый поток, создаст в нем список, а потом данные сразу же попадут во View через метод showResult. При этом приложение продолжает жить активной жизнью, без задержек и перерасхода памяти.

Кстати, мы еще совсем ничего не знаем о блоке View. По сути, код тут может быть совершенно произвольным, никак не завязанным на реактивном программировании. Все, что от View требуется, — реализовать методы inProgress и showResult, вызываемые из связующего блока.

public void inProgress(Boolean inProgress)
{
    int visibility=(inProgress)? View.VISIBLE : View.GONE;
    progress.setVisibility(visibility);
}
public void showResult(UserList list)
{
    view.setText(...);
    ...
}

Чтобы паттерн заработал, осталось только инициализировать объекты на основе созданных классов — сделать это можно где-нибудь в MainActivity.

model = new Model("url");
presenterSimple = new PresenterSimple(this, model);
presenterSimple.loadDatawithRx("url");

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

Рис. 3. Многообразие портов ReactiveX
Рис. 3. Многообразие портов ReactiveX
 

Лямбда-выражения

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

В Java 8 появились так называемые лямбда-выражения, которые очень похожи на функции в С-подобных языках. Не так давно они стали доступны и в Android (с обратной совместимостью API до версии 23), теперь можно сделать код более читаемым и лаконичным.

observableHeavyOp=Observable.create(v->v.onNext(prepareList()))
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidSchedulers.mainThread());
...
myObservable.subscribe(userList ->
    {view.inProgress(false);view.showResult(userList);}
);
 

ReactiveX v.2

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

 

Outro

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

1 комментарий

  1. Аватар

    baragoz

    14.12.2016 в 12:20

Оставить мнение