14、观察者模式进阶:响应式编程中的观察者模式、RxJava与观察者模式

好,咱们今天聊点硬核的。

前面几章我们把观察者模式的基本套路讲透了。但说实话,传统观察者模式在实际项目中,用起来总有点「不够爽」的感觉。你想想看,一个按钮点击触发一堆回调,数据流要手动管理订阅和取消订阅,多线程环境下还得自己处理同步……嗯,我早年在一个金融项目中就吃过这个亏。

传统观察者模式的痛点

先说说我踩过的坑。那时候做一个实时行情系统,用传统观察者模式监听股票价格变化。代码写出来是这样的:

// 传统观察者模式
public class StockPriceSubject {
    private List<Observer> observers = new ArrayList<>();
    private double price;
    
    public void attach(Observer o) { observers.add(o); }
    public void detach(Observer o) { observers.remove(o); }
    
    public void setPrice(double price) {
        this.price = price;
        notifyObservers();
    }
    
    private void notifyObservers() {
        for (Observer o : observers) {
            o.update(price);
        }
    }
}

看着挺规整对吧?但实际跑起来问题就来了:

  • 数据流不可控:价格更新频率一高,观察者根本来不及处理
  • 线程问题:价格推送来自网络线程,UI更新必须在主线程,手动切换太痛苦
  • 错误处理缺失:某个观察者抛异常,整个通知链就断了
  • 背压问题:生产者太快,消费者太慢,内存直接爆掉

我当时就在想,有没有一种模式,能把观察者模式「升级」一下,让它能处理这些现实问题?

响应式编程:观察者模式的「进化版」

说白了,响应式编程就是观察者模式 + 迭代器模式 + 函数式编程的合体。它把数据看作一条「流」,你可以对这个流做各种操作——过滤、转换、合并、分流。

核心思想就四个字:一切皆流

我画了一张图,帮你理解这个进化关系:

观察者模式 → 响应式编程 进化图 传统观察者模式 Subject → Observer 手动管理订阅/取消订阅 同步通知,无背压控制 进化 响应式编程 Observable → Observer 操作符链式处理数据流 背压、线程调度、错误处理 响应式编程三大核心概念 数据流 Observable 操作符 Operators 订阅者 Observer

RxJava 中的观察者模式

RxJava 是响应式编程在 Java 世界最经典的一个实现。它的核心接口,说白了就是观察者模式的「豪华升级版」。

咱们先看最基础的几个角色:

传统观察者 RxJava 对应 说明
Subject Observable 数据源,发射数据流
Observer Observer / Subscriber 订阅者,接收数据
attach() subscribe() 建立订阅关系
update() onNext() / onError() / onComplete() 三个回调方法

看到没?RxJava 把观察者模式里的 update() 拆成了三个方法。为什么要这么干?

我当年做项目时,最头疼的就是「数据流什么时候结束」这个问题。传统观察者模式里,观察者永远不知道数据流是否终止。RxJava 的 onComplete() 就解决了这个问题。还有 onError(),让错误处理变得优雅多了。

实战:用 RxJava 重构股票行情系统

还记得我前面说的那个行情系统吗?用 RxJava 重写之后,代码清爽多了:

// RxJava 版本的股票价格监听
Observable<Double> priceStream = Observable.create(emitter -> {
    // 模拟价格推送
    new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            double price = 100 + Math.random() * 10;
            emitter.onNext(price);
            Thread.sleep(100);
        }
        emitter.onComplete();
    }).start();
});

// 订阅并处理
priceStream
    .filter(price -> price > 105)  // 只关注价格超过105的
    .map(price -> String.format("当前价格: %.2f", price))
    .subscribe(
        msg -> System.out.println(msg),  // onNext
        err -> System.err.println("出错: " + err),  // onError
        () -> System.out.println("数据流结束")  // onComplete
    );

关键变化:

  • 数据流变成了 Observable,可以链式调用操作符
  • filter() 和 map() 让数据处理变得声明式
  • 三个回调各司其职,不再混在一起

操作符:观察者模式的「超能力」

操作符是 RxJava 最强大的地方。它本质上是对观察者模式的一种「装饰」——每个操作符都包装了原始的 Observable,返回一个新的 Observable。

我常用的几个操作符:

操作符 作用 类比传统观察者
map() 转换数据 手动在 update() 里做转换
filter() 过滤数据 在 update() 里加 if 判断
merge() 合并多个数据流 需要自己维护多个 Subject
debounce() 防抖,控制频率 传统模式很难优雅实现
subscribeOn() 指定数据生产线程 传统模式需要手动 new Thread()
observeOn() 指定数据消费线程 传统模式需要 Handler/runOnUiThread

我的经验:

debounce() 这个操作符,我在搜索框自动补全功能里用过无数次。用户输入时,传统做法是每次按键都发请求,用 debounce(300ms) 之后,只有用户停止输入 300ms 后才发请求,性能提升非常明显。

背压:观察者模式没解决的问题

传统观察者模式有一个致命缺陷:如果生产者比消费者快,数据会堆积。我当年在行情系统里就遇到过,价格更新太快,UI 线程根本来不及刷新,最后内存溢出。

RxJava 用背压(Backpressure)机制解决了这个问题。说白了就是:消费者告诉生产者「你慢点,我吃不消了」。

// 带背压的 Observable
Flowable<Double> priceFlow = Flowable.create(emitter -> {
    for (int i = 0; i < 1000; i++) {
        emitter.onNext(Math.random() * 10);
    }
    emitter.onComplete();
}, BackpressureStrategy.BUFFER);  // 背压策略

priceFlow
    .onBackpressureDrop()  // 消费不过来就丢弃
    .observeOn(Schedulers.computation())
    .subscribe(System.out::println);

注意:

背压策略不是万能的。BUFFER 策略可能导致内存溢出,DROP 策略可能丢失重要数据。我建议根据业务场景选择合适的策略:

  • BUFFER:适合数据量小、偶尔突发的场景
  • DROP:适合实时性要求高、可以丢数据的场景(如股票行情)
  • LATEST:适合只关心最新数据的场景(如传感器读数)

Subject:既是观察者,又是被观察者

RxJava 里有一个特殊的角色叫 Subject。它既可以作为 Observable 发射数据,又可以作为 Observer 订阅其他数据源。

我常用的是 PublishSubject:

// PublishSubject 示例
PublishSubject<String> subject = PublishSubject.create();

// 订阅者1
subject.subscribe(msg -> System.out.println("订阅者1: " + msg));
// 订阅者2
subject.subscribe(msg -> System.out.println("订阅者2: " + msg));

// 发射数据
subject.onNext("Hello");
subject.onNext("World");

这个模式在事件总线(Event Bus)里用得特别多。我做过一个模块通信系统,就是用 Subject 做中介,各个模块之间完全解耦。

从 RxJava 到现代响应式框架

RxJava 的思想影响了很多现代框架。比如:

  • Spring WebFlux:基于 Reactor(响应式流规范),底层就是观察者模式
  • Android 的 LiveData:Google 官方推荐的观察者模式实现
  • Java 9 的 Flow API:标准库级别的响应式流支持

但不管框架怎么变,核心思想没变:数据流 + 操作符 + 订阅者。你只要理解了观察者模式,理解这些框架就只是语法层面的差异了。

总结一下:

响应式编程不是要取代观察者模式,而是把观察者模式「武装到牙齿」。它解决了传统观察者模式在异步、并发、错误处理、背压等方面的短板。说白了,就是让观察者模式能应对真实世界的复杂场景。

我个人建议,如果你还在用传统观察者模式写回调地狱,不妨试试 RxJava 或者 Reactor。刚开始可能觉得 API 有点多,但用顺手之后,你会觉得「回不去了」。

公众号:蓝海资料掘金营,微信deep3321