Java 观察者模式 3 种实现对比:原生接口 vs 自定义 vs 事件驱动

观察者模式是软件设计中最常用的行为型模式之一,它定义了对象间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动收到通知。在Java生态中,实现观察者模式主要有三种典型方式:使用Java原生 Observable/Observer 接口、自定义观察者接口,以及基于Spring框架的事件驱动模型。本文将深入剖析这三种实现方式的优劣,并通过完整代码示例展示其应用场景。

1. 观察者模式核心概念

观察者模式(Observer Pattern)又称为发布-订阅模式(Publish-Subscribe),包含两个核心角色:

  • Subject(主题) :维护观察者列表,提供注册/注销方法,状态变更时通知所有观察者
  • Observer(观察者) :定义更新接口,用于接收主题通知

模式优势在于解耦主题与观察者,使得:

  • 主题无需关心具体观察者类型
  • 可以动态添加/移除观察者
  • 符合开闭原则(对扩展开放,对修改关闭)

典型应用场景包括:

  • GUI事件处理(如按钮点击事件)
  • 分布式系统的消息通知
  • 监控系统状态变化
  • MVC模式中的模型-视图同步

2. Java原生实现:Observable/Observer

Java在 java.util 包中提供了内置的观察者模式实现,包含两个关键类:

// 被观察者继承java.util.Observable
public class WeatherData extends Observable {
    private float temperature;
    
    public void setMeasurements(float temp) {
        this.temperature = temp;
        setChanged();  // 标记状态已改变
        notifyObservers();  // 无参通知
    }
    
    public float getTemperature() {
        return temperature;
    }
}

// 观察者实现java.util.Observer接口
public class CurrentConditionsDisplay implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData data = (WeatherData)o;
            System.out.println("当前温度: " + data.getTemperature());
        }
    }
}

// 使用示例
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherData.addObserver(display);
weatherData.setMeasurements(25.5f);

优点

  • 开箱即用,无需额外实现
  • 线程安全的通知机制( notifyObservers 方法同步)

缺点

  • Observable 是类而非接口,限制了复用
  • 无法实现更精细的通知控制
  • JDK9后已被标记为 @Deprecated(since="9")

适用场景 :简单的单线程观察者需求,且不需要扩展 Observable 类功能的情况。

3. 自定义接口实现

更灵活的方式是自定义观察者接口,典型实现如下:

// 自定义主题接口
public interface Subject<T> {
    void registerObserver(Observer<T> o);
    void removeObserver(Observer<T> o);
    void notifyObservers(T data);
}

// 自定义观察者接口
public interface Observer<T> {
    void update(T data);
}

// 具体主题实现
public class ConcreteSubject implements Subject<String> {
    private final Set<Observer<String>> observers = new CopyOnWriteArraySet<>();
    
    @Override
    public void registerObserver(Observer<String> o) {
        observers.add(o);
    }
    
    @Override
    public void notifyObservers(String data) {
        observers.forEach(o -> o.update(data));
    }
}

// 具体观察者实现
public class ConcreteObserver implements Observer<String> {
    @Override
    public void update(String data) {
        System.out.println("收到数据: " + data);
    }
}

关键优化点

  • 使用泛型支持不同类型的数据通知
  • CopyOnWriteArraySet 保证线程安全
  • 接口化设计符合依赖倒置原则

对比原生实现的优势

维度 原生实现 自定义实现
类型安全 使用Object传递数据 泛型支持
扩展性 受限于Observable类 完全自主控制
线程模型 同步通知 可灵活选择同步/异步
接口设计 类继承 接口实现

适用场景 :需要精细控制通知逻辑、支持多种数据类型或特殊线程模型的复杂场景。

4. Spring事件驱动模型

在Spring框架中,观察者模式通过事件机制实现,核心组件包括:

// 自定义事件(继承ApplicationEvent)
public class OrderCreatedEvent extends ApplicationEvent {
    private String orderId;
    
    public OrderCreatedEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }
    // getter...
}

// 发布者(注入ApplicationEventPublisher)
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void createOrder() {
        // 业务逻辑...
        publisher.publishEvent(new OrderCreatedEvent(this, "12345"));
    }
}

// 监听器(实现ApplicationListener或使用@EventListener)
@Component
public class InventoryListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("处理订单创建: " + event.getOrderId());
    }
}

Spring模型的独特优势

  1. 松耦合 :发布者与监听器通过事件对象交互,完全解耦
  2. 注解支持 @EventListener 简化监听器定义
  3. 条件过滤 :支持 @EventListener(condition = "...")
  4. 异步处理 :配合 @Async 实现异步事件处理
  5. 事务绑定 :支持 @TransactionalEventListener 绑定事务阶段

三种实现方式对比

特性 Java原生 自定义接口 Spring事件
实现复杂度
功能扩展性 优秀 优秀
线程控制 仅同步 可自定义 支持异步
框架依赖 JDK内置 需Spring
适用场景 简单同步通知 通用场景 Spring应用

5. 高级应用与陷阱规避

5.1 内存泄漏防护

观察者模式常见的内存泄漏场景:

// 错误示例:观察者未及时注销
public class LeakDemo {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        while(true) {
            Observer observer = new ConcreteObserver();
            subject.registerObserver(observer);
            // 忘记调用removeObserver
        }
    }
}

解决方案

  1. 使用弱引用(WeakReference)存储观察者
  2. 显式提供注销接口并在适当时机调用
  3. 对于集合类观察者,使用 WeakHashMap 存储

5.2 性能优化策略

当观察者数量庞大时,可采用:

// 分级通知示例
public class HierarchicalNotifier {
    private Map<EventType, Set<Observer>> observersByType = new EnumMap<>(EventType.class);
    
    public void notifyObservers(EventType type, Object data) {
        observersByType.getOrDefault(type, Collections.emptySet())
                      .forEach(o -> o.update(data));
    }
}

其他优化技巧:

  • 对高频事件采用批量通知
  • 使用享元模式复用观察者实例
  • 对IO密集型观察者采用异步处理

5.3 分布式观察者模式

在微服务架构中,可通过以下方式实现跨服务观察:

// 使用Spring Cloud Stream实现跨服务事件
@EnableBinding(Source.class)
public class DistributedEventPublisher {
    @Autowired
    private Source source;
    
    public void publishEvent(String message) {
        source.output().send(MessageBuilder.withPayload(message).build());
    }
}

// 消费者端
@StreamListener(Sink.INPUT)
public void handleMessage(String message) {
    System.out.println("Received: " + message);
}

6. 设计模式组合应用

观察者模式常与其他模式协同工作:

与中介者模式结合

// 使用中介者管理复杂观察关系
public class NotificationMediator {
    private Map<String, List<Observer>> topicSubscribers = new HashMap<>();
    
    public void subscribe(String topic, Observer observer) {
        topicSubscribers.computeIfAbsent(topic, k -> new ArrayList<>()).add(observer);
    }
    
    public void publish(String topic, Object message) {
        topicSubscribers.getOrDefault(topic, Collections.emptyList())
                       .forEach(o -> o.update(message));
    }
}

与责任链模式结合

// 观察者形成处理链
public class ChainedObserver implements Observer {
    private Observer next;
    
    @Override
    public void update(Object data) {
        if(canHandle(data)) {
            // 处理逻辑...
        } else if(next != null) {
            next.update(data);
        }
    }
}

在实际项目中,观察者模式的实现选择需综合考虑以下因素:

  • 项目架构(是否使用Spring等框架)
  • 性能要求(同步/异步,吞吐量需求)
  • 扩展性需求(是否需要支持多种事件类型)
  • 团队技术栈熟悉度

对于新启动的Spring项目,推荐优先使用Spring事件机制;而在基础组件开发或非Spring环境中,自定义接口实现提供了最大的灵活性。Java原生实现虽简单但已逐渐被更现代的方案取代。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐