Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RxJava: the observer pattern on steroids
Search
Alexey Vakhrenev
August 12, 2015
Programming
240
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
RxJava: the observer pattern on steroids
Alexey Vakhrenev
August 12, 2015
Other Decks in Programming
See All in Programming
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.6k
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.7k
symfony/aiとlaravel/boost
77web
0
110
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
150
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
190
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
820
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
630
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
330
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
150
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
150
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
230
A2UI という光を覗いてみる
satohjohn
1
170
Featured
See All Featured
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
KATA
mclloyd
PRO
35
15k
How STYLIGHT went responsive
nonsquared
100
6.2k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
180
sira's awesome portfolio website redesign presentation
elsirapls
0
290
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
550
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.5k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
150
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
880
The Cost Of JavaScript in 2023
addyosmani
55
10k
Transcript
RxJava: observer pattern on steroids Alexey Vakhrenev
[email protected]
Agenda • The observer pattern ◦ usages ◦ problems and
limitations • Towards a better observer • RxJava to the rescue
Observer pattern • Behavioral pattern • Used in UI toolkits
• Key component of MVC
Observer pattern Model Notifications
Observer pattern https://en.wikipedia.org/wiki/Observer_pattern
Pros • Asynchronous • Loose coupling ◦ between observer and
subject
Drag-and-drop
Drag-and-drop 1. Mouse down 2. Mouse move ◦ until mouse
up calculating Δ‘s while moving
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); }
Drag-and-drop interface DragListener { void onMouseDragged(Line drag); }
Drag-and-drop void listenToTheDrag(MouseController mouse, DragListener listener) { ??? }
Drag-and-drop Line line = null; MouseDownListener downListener = e ->
{ line = new Line(e.position); mouse.addMouseUpListener(upListener); mouse.addMouseMoveListener(moveListener); };
Drag-and-drop MouseMoveListener moveListener = e -> { line = line.endAt(e.position);
listener.onMouseDragged(line); };
Drag-and-drop MouseUpListener upListener = new MouseUpListener() { public void onMouseUp(MouseEvent
e) { mouse.removeMouseMoveListener(moveListener); mouse.removeMouseUpListener(this); line = null; } };
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); }
Cons • Tight coupling ◦ between observers dealing with single
concern • Promotes shared mutable state
Cons • Resource management https://en.wikipedia.org/wiki/Lapsed_listener_problem
Cons • Abstraction ◦ no way to abstract over event
source • Code reuse ◦ no way to express common patterns in reusable fasion
Cons • High semantic distance ◦ similarity between problem definition
and solution ◦ makes code hard to understand ◦ makes problem definition hard to infer from code
Can we do better?
Let’s improve it Step by step
1.Generic observer interface Observer<T> { void onEvent(T event); }
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); }; }
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); }; }
1.Generic observer //with default methods Observer<String> original = System.out::println; Observer<String>
decorated = original .dropRepeated() .filter((s) -> !s.isEmpty()) .map(String::toLowerCase);
2.Generic Observable interface Observable<T> { void subscribe(Observer<? super T> observer);
void unsubscribe(Observer<? super T> observer); }
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); } }; }
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); } }; }
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?
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) { //... }
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); }
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)); }
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(); }; }; }
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();}; }; }
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; }; }
3.First-class subscription But what if Observable<T> obs = ... Observable<Boolean>
testZip = Observable.zip( obs, obs.take(5), (left, right) -> left == right );
3.First-class subscription But what if Observable<T> obs = ... Observable<Boolean>
testZip = Observable.zip( obs, obs.take(5), (left, right) -> left == right ); );
4.Unsubscribe signal interface Observer<T> { void onEvent(T event); void onCompleted();
void onError(Throwable e); }
4.Unsubscribe signal signal ::= onEvent* (onCompleted | onError)? V V
X
A lot can be implemented once and for all
We don’t have to do it ourselves
RxJava • The observer pattern done right • Foundational interfaces
and contracts • Extensive set of operations
RxJava public interface Observer<T> { public void onCompleted(); public void
onError(Throwable e); public void onNext(T t); }
RxJava rx.Subscription • Encapsulates unsubscribe action • Idempotent public interface
Subscription { public void unsubscribe(); public boolean isUnsubscribed(); }
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) { //... } }
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) )); }); }
RxJava rx.Observable<T> • More than 300 methods
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) { //... }
RxJava rx.Subject<T, R> • Observable & Observer • Acts as
multicaster public abstract class Subject<T, R> extends Observable<R> implements Observer<T>{ }
Drag-and-drop interface RxMouseController { Observable<MouseEvent> mouseUps(); Observable<MouseEvent> mouseDowns(); Observable<MouseEvent> mouseMoves();
}
Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns(). }
Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns(). }
Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns().flatMap(start -> {
return mouse.mouseMoves(). });}
Drag-and-drop static Observable<Line> dragsRx(RxMouseController mouse) { return mouse.mouseDowns().flatMap(start -> {
return mouse.mouseMoves(). });}
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)) });}
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)) });}
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()); });}
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
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)) ) ); }
Schedulers • observables are asynchronous • so they have a
notion of time • we can parametrize ◦ when and ◦ where operaion happen
Schedulers
Schedulers Scheduler AWT_THREAD = Schedulers.from(SwingUtilities::invokeLater); observable.observeOn(AWT_THREAD);
Demo
Overloading observeQuote("MSFT") .observeOn(AWT_THREAD) .map((quote) -> quote.getBidPrice()) .filter((bid) -> !Double.isNaN(bid)) .map((bid)
-> Double.toString(bid)) .subscribe((bid) -> label.setText(bid));
Overloading observeQuote("MSFT") .observeOn(AWT_THREAD) .map((quote) -> quote.getBidPrice()) .filter((bid) -> !Double.isNaN(bid)) .map((bid)
-> Double.toString(bid)) .subscribe((bid) -> label.setText(bid));
MissingBackpressureException
Overloading observeQuote("MSFT") .observeOn(AWT_THREAD) .map((quote) -> quote.getBidPrice()) .filter((bid) -> !Double.isNaN(bid)) .map((bid)
-> Double.toString(bid)) .subscribe((bid) -> label.setText(bid));
Overloading observeQuote("MSFT") .observeOn(AWT_THREAD) .map((quote) -> quote.getBidPrice()) .filter((bid) -> !Double.isNaN(bid)) .map((bid)
-> Double.toString(bid)) .subscribe((bid) -> label.setText(bid)); Ignores backpressure
Overloading
Overloading observeQuote("MSFT") .onBackpressureLatest() .observeOn(AWT_THREAD) .map((quote) -> quote.getBidPrice()) .filter((bid) -> !Double.isNaN(bid))
.map((bid) -> Double.toString(bid)) .subscribe((bid) -> label.setText(bid)); Handles backpressure
Summary • Observer pattern has drawbacks • It can be
improved • Try RxJava instead
Questions?
Thank you!