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
symfony/aiとlaravel/boost
77web
0
110
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7.2k
どこまでゆるくて許されるのか
tk3fftk
0
290
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
260
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
430
AIで効率化できた業務・日常
ochtum
0
160
LaravelLive Japan の裏方のすべて — 第188回 PHP勉強会@東京 (2026-06-24)
suguruooki
2
140
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
280
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.6k
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
620
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
620
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
310
Featured
See All Featured
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.3k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
Making Projects Easy
brettharned
120
6.7k
Automating Front-end Workflow
addyosmani
1370
210k
Amusing Abliteration
ianozsvald
1
220
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Crafting Experiences
bethany
1
200
Designing for humans not robots
tammielis
254
26k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
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!