Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxJava: the observer pattern on steroids

RxJava: the observer pattern on steroids

Alexey Vakhrenev

August 12, 2015
Tweet

Other Decks in Programming

Transcript

  1. Agenda • The observer pattern ◦ usages ◦ problems and

    limitations • Towards a better observer • RxJava to the rescue
  2. Drag-and-drop interface MouseController { void addMouseUpListener(MouseUpListener listener); void addMouseDownListener(MouseDownListener listener);

    void addMouseMoveListener(MouseMoveListener listener); void removeMouseUpListener(MouseUpListener listener); void removeMouseDownListener(MouseDownListener listener); void removeMouseMoveListener(MouseMoveListener listener); }
  3. Drag-and-drop Line line = null; MouseDownListener downListener = e ->

    { line = new Line(e.position); mouse.addMouseUpListener(upListener); mouse.addMouseMoveListener(moveListener); };
  4. Drag-and-drop MouseUpListener upListener = new MouseUpListener() { public void onMouseUp(MouseEvent

    e) { mouse.removeMouseMoveListener(moveListener); mouse.removeMouseUpListener(this); line = null; } };
  5. Drag-and-drop void listenToTheDrag(MouseController mouse, final DragListener listener) { AtomicReference<Line> line

    = new AtomicReference<>(null); MouseMoveListener moveListener = e -> { line.set(line.get().endAt(e.position)); }; MouseUpListener upListener = new MouseUpListener() { public void onMouseUp(MouseEvent e) { mouse.removeMouseMoveListener(moveListener); mouse.removeMouseUpListener(this); line.set(null); } }; MouseDownListener downListener = e -> { line.set(new Line(e.position)); listener.onMouseDragged(line.get()); mouse.addMouseUpListener(upListener); mouse.addMouseMoveListener(moveListener); }; mouse.addMouseDownListener(downListener); }
  6. Cons • Tight coupling ◦ between observers dealing with single

    concern • Promotes shared mutable state
  7. Cons • Abstraction ◦ no way to abstract over event

    source • Code reuse ◦ no way to express common patterns in reusable fasion
  8. Cons • High semantic distance ◦ similarity between problem definition

    and solution ◦ makes code hard to understand ◦ makes problem definition hard to infer from code
  9. 1.Generic observer static <T> Observer<T> dropRepeated(Observer<? super T> self) {

    AtomicReference<T> old = new AtomicReference<>(); return event -> { if (!Objects.equals(event, old.getAndSet(event))) self.onEvent(event); }; }
  10. 1.Generic observer //transforms events static <T, R> Observer<R> map(Observer<? super

    T> self, Func1<? super R, ? extends T> fn) { return event -> self.onEvent(fn.call(event)); } //filter out some events static <T> Observer<T> filter(Observer<? super T> self, Func1<? super T, Boolean> predicate) { return (event) -> { if (predicate.call(event)) self.onEvent(event); }; }
  11. 1.Generic observer //with default methods Observer<String> original = System.out::println; Observer<String>

    decorated = original .dropRepeated() .filter((s) -> !s.isEmpty()) .map(String::toLowerCase);
  12. 2.Generic Observable default Observable<T> dropRepeated() { Observable<T> self = this;

    return new Observable<T>() { Map<Observer, Observer> observers = new HashMap<>(); public void subscribe(Observer<? super T> observer) { Observer<? super T> decorated = observer.dropRepeated(); observers.put(observer, decorated); self.subscribe(decorated); } public void unsubscribe(Observer<? super T> observer) { Observer decorated = observers.get(observer); self.unsubscribe(decorated); } }; }
  13. 2.Generic Observable default <R> Observable<R> map(Func1<? super T, ? extends

    R> fn) { Observable<T> self = this; return new Observable<R>() { Map<Observer, Observer> observers = new HashMap<>(); public void subscribe(Observer<? super R> observer) { Observer<? super T> decorated = observer.map(fn); observers.put(observer, decorated); self.subscribe(decorated); } public void unsubscribe(Observer<? super R> observer) { Observer decorated = observers.get(observer); self.unsubscribe(decorated); } }; }
  14. 2.Generic Observable default <R> Observable<R> map(Func1<? super T, ? extends

    R> fn) { Observable<T> self = this; return new Observable<R>() { Map<Observer, Observer> observers = new HashMap<>(); public void subscribe(Observer<? super R> observer) { Observer<? super T> decorated = observer.map(fn); observers.put(observer, decorated); self.subscribe(decorated); } public void unsubscribe(Observer<? super R> observer) { Observer decorated = observers.get(observer); self.unsubscribe(decorated); } }; } See the pattern?
  15. 3.First-class subscription public void subscribe(Observer<? super T> observer) { //...

    } public void unsubscribe(Observer<? super T> observer) { //... } public Runnable subscribe(Observer<? super T> observer) { //... return () -> unsubscribe(observer); } private void unsubscribe(Observer<? super T> observer) { //... }
  16. 3.First-class subscription interface Observable<T> { /** * @param observer observer

    to subscribe * @return handle to free all the resources * associated with the observer */ Runnable subscribe(Observer<? super T> observer); }
  17. 3.First-class subscription default Observable<T> dropRepeated() { Observable<T> self = this;

    return observer -> self.subscribe(observer.dropRepeated()); } default <R> Observable<R> map(Func1<? super T, ? extends R> fn) { Observable<T> self = this; return observer -> self.subscribe(observer.map(fn)); }
  18. 3.First-class subscription static <T> Observable<T> merge(Observable<? extends T> obs1, Observable<?

    extends T> obs2) { return observer -> { Runnable r1 = obs1.addListener(observer); Runnable r2 = obs2.addListener(observer); return () -> { r1.run(); r2.run(); }; }; }
  19. 3.First-class subscription static <T1, T2, R> Observable<R> zip(Observable<T1> obs1, Observable<T2>

    obs2, Func2<? super T1, ? super T2, R> fn) { return observer -> { Queue<T1> q1 = new ArrayDeque<>(); Queue<T2> q2 = new ArrayDeque<>(); Runnable r1 = obs1.subscribe(t1 -> { T2 t2 = q2.poll(); if (t2 == null) { q1.add(t1); } else { observer.onEvent(fn.call(t1, t2)); } }); Runnable r2 = obs2.subscribe(t2 -> { T1 t1 = q1.poll(); if (t1 == null) { q2.add(t2); } else { observer.onEvent(fn.call(t1, t2)); } }); return () -> {r1.run(); r2.run();}; }; }
  20. 3.First-class subscription default Observable<T> take(int max_elements) { Observable<T> self =

    this; AtomicInteger counter = new AtomicInteger(max_elements); return observer -> { RunnableHolder holder = new RunnableHolder(); holder.hold(self.subscribe(event -> { observer.onEvent(event); if (counter.decrementAndGet() == 0) { holder.run(); } })); return holder; }; }
  21. 3.First-class subscription But what if Observable<T> obs = ... Observable<Boolean>

    testZip = Observable.zip( obs, obs.take(5), (left, right) -> left == right );
  22. 3.First-class subscription But what if Observable<T> obs = ... Observable<Boolean>

    testZip = Observable.zip( obs, obs.take(5), (left, right) -> left == right ); );
  23. RxJava • The observer pattern done right • Foundational interfaces

    and contracts • Extensive set of operations
  24. RxJava public interface Observer<T> { public void onCompleted(); public void

    onError(Throwable e); public void onNext(T t); }
  25. RxJava rx.Subscription • Encapsulates unsubscribe action • Idempotent public interface

    Subscription { public void unsubscribe(); public boolean isUnsubscribed(); }
  26. RxJava rx.Subscriber<T> • Observer & Subscription • Attach dispose callbacks

    via add public abstract class Subscriber<T> implements Observer<T>, Subscription { public final void add(Subscription s) { //... } }
  27. RxJava rx.Observable<T> • Subscriber<? super T> -> void Observable<ActionEvent> actionsOf(JButton

    button) { return Observable.create((subscriber) -> { ActionListener listener = subscriber::onNext; button.addActionListener(listener); subscriber.add(Subscriptions.create(() -> button.removeActionListener(listener) )); }); }
  28. RxJava rx.Observable<T> • ...most of them are implemented via lift

    public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>> { // cover for generics insanity } public final <R> Observable<R> lift(final Operator<? extends R, ? super T> lift) { //... }
  29. RxJava rx.Subject<T, R> • Observable & Observer • Acts as

    multicaster public abstract class Subject<T, R> extends Observable<R> implements Observer<T>{ }
  30. Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns().flatMap(start -> {

    return mouse.mouseMoves() .map(end -> new Line(start.position, end.position)) });}
  31. Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns().flatMap(start -> {

    return mouse.mouseMoves() .map(end -> new Line(start.position, end.position)) });}
  32. Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns().flatMap(start -> {

    return mouse.mouseMoves() .map(end -> new Line(start.position, end.position)) .takeUntil(mouse.mouseUps()); });}
  33. Resolve instrument • Take instrument by symbols • If not

    found: search futures product ◦ if found: find front futures by trading day Instrument Futures Product Futures Trading day Changes over time
  34. Resolve instrument Observable<Instrument> resolveToInstrument(String symbol) { return getInstrumentBySymbol(symbol) .onErrorResumeNext( getFuturesProduct(symbol)

    .switchMap(product -> getTradingDay() .map(day -> product.getFrontSymbol(day)) .switchMap(front -> getInstrumentBySymbol(front)) ) ); }
  35. Schedulers • observables are asynchronous • so they have a

    notion of time • we can parametrize ◦ when and ◦ where operaion happen