Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
RxJava: the observer pattern on steroids
Search
Alexey Vakhrenev
August 12, 2015
Programming
250
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主導のプログラミング学習アプリを作ってみた
econanringo
0
180
S3 TablesのIcebergデータに、AIエージェントからアクセスしてみた / 20261016developersio2026joetsu-accessing-iceberg-in-s3tables-via-agent
kasacchiful
PRO
0
150
パスワード再設定メールのURLについて知っておこう
uyuki234
0
160
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1.2k
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
0
140
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
610
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
220
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
2
900
TechTown - Creating Composable Callables in Contemporary C++
rollbear
0
130
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
430
[Dev Group Paris Octobre 26] Agentforce, SIforce, Claudeforce, Slackbot… on s'y retrouve ?
nabondance
0
110
Featured
See All Featured
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
850
The Pragmatic Product Professional
lauravandoore
37
7.5k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
580
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
710
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
Optimizing for Happiness
mojombo
378
71k
Designing Powerful Visuals for Engaging Learning
tmiket
1
610
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.2k
Producing Creativity
orderedlist
PRO
348
41k
So, you think you're a good person
axbom
PRO
2
2.2k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
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!