Подтвердить что ты не робот

Создание наблюдаемых из обычных событий Java

Каков наилучший способ создания Rx-Java Observable из классического шаблона событий Java? То есть, учитывая

class FooEvent { ... }

interface FooListener {
  void fooHappened(FooEvent arg);
}

class Bar {
  public void addFooListener(FooListener l);
  public void removeFooListener(FooListener l);
}

Я хочу реализовать

Observable<FooEvent> fooEvents(Bar bar);

Реализация, с которой я столкнулся, это:

Observable<FooEvent> fooEvents(Bar bar) {
  return Observable.create(new OnSubscribeFunc<FooEvent>() {
    public Subscription onSubscribe(Observer<? super FooEvent> obs) {
      FooListener l = new FooListener() {
        public void fooHappened(FooEvent arg) {
          obs.onNext(arg);
        }
      };

      bar.addFooListener(l);

      return new Subscription() {
        public void unsubscribe() {
          bar.removeFooListener(l);
        }
      };
    }
  }); 
}

Однако мне это не очень нравится:

  • он довольно многословный;

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

Есть ли лучшее решение?

Требования:

  • Работа с существующими реализациями шаблонов событий без их изменения (если бы я контролировал этот код, я мог бы уже записать его для возврата Observable, который мне нужен).

  • Получение ошибок компилятора, если/при изменении исходного API. Не работает с Object вместо фактического типа аргумента события или с строками имен свойств.

4b9b3361

Ответ 1

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

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

Ответ 2

Ваша реализация абсолютно правильна.

это довольно многословный

Он становится намного менее подробным с lambdas (пример для RxJava 2):

Observable<FooEvent> fooEvents(Bar bar) {
    return Observable.create(emitter -> {
        FooListener listener = event -> emitter.onNext(event);
        bar.addFooListener(listener);
        emitter.setCancellable(() -> bar.removeFooListener(listener));
    }); 
}

в идеале не должно быть слушателей, если нет наблюдателей, а один слушатель в противном случае

Вы можете использовать оператор share(), который делает ваши наблюдаемые горячие, т.е. все подписчики используют одну подписку. Он автоматически подписывается на первого абонента и отменяет подписку, если последний отменил подписку:

fooEvents(bar).share()

Ответ 3

Я предполагаю, что вы можете иметь тот же суп, просто повторно нагретый, если вы используете другой слой слушателей в качестве моста между фактическими обратными вызовами и вашим наблюдателем. Actual callback → bridge callback → Observer.

Преимущества:

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

Ex (обратите внимание, как мало создается наблюдаемое закрытие):

class LocationService @Inject constructor(private val googleApiClient: GoogleApiClient) : ConnectionCallbacks{

    val locationObservable: Observable<Location>    

    private var passToObservable: (Location?) -> Unit = {}

    init {
        locationObservable = Observable.create<Location> { subscription ->
            passToObservable = { location ->
                subscription.onNext(location)
            }
        }.doOnSubscribe {
            googleApiClient.registerConnectionCallbacks(this)
            googleApiClient.connect()
        }.doOnUnsubscribe {
            googleApiClient.unregisterConnectionCallbacks(this)
        }
    }

    override fun onConnected(connectionHint: Bundle?) {
        val location = LocationServices.FusedLocationApi.getLastLocation(googleApiClient)
        passToObservable(location)
    }

    override fun onConnectionSuspended(cause: Int) {
        //...
    }
}

Ответ 4

Если вам нужно что-то простое и встроенное, дайте этому подходу попробовать http://examples.javacodegeeks.com/core-java/beans/bean-property-change-event-listener/

java.beans.PropertyChangeEvent;
java.beans.PropertyChangeListener;
java.beans.PropertyChangeSupport;

С сайта есть фрагмент, который показывает, как его использовать

package com.javacodegeeks.snippets.core;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class BeanPropertyChangeEventListener {

    public static void main(String[] args) throws Exception {

        Bean bean = new Bean();
        bean.addPropertyChangeListener(new MyPropertyChangeListener());

        bean.setProperty1("newProperty1");
        bean.setProperty2(123);


        bean.setProperty1("newnewProperty1");
        bean.setProperty2(234);

    }

    public static class MyPropertyChangeListener implements PropertyChangeListener {
        // This method is called every time the property value is changed
        public void propertyChange(PropertyChangeEvent evt) {
            System.out.println("Name = " + evt.getPropertyName());

  System.out.println("Old Value = " + evt.getOldValue());

  System.out.println("New Value = " + evt.getNewValue());

  System.out.println("**********************************");
        }
    }

    public static class Bean {

        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

        // Property property1
        private String property1;
        // Property property2
        private int property2;

        public String getProperty1() {
            return property1;
        }
        public void setProperty1(String property1) {
            pcs.firePropertyChange("property1", this.property1, property1);
            this.property1 = property1;
        }

        public int getProperty2() {
            return property2;
        }
        public void setProperty2(int property2) {
            pcs.firePropertyChange("property2", this.property2, property2);
            this.property2 = property2;
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            pcs.addPropertyChangeListener(listener);
        }

    }

}

это довольно просто