Java观察者模式实现方式与测试方法

一、实现方式

  1. 自定义实现
    通过手动定义SubjectObserver接口,实现一对多依赖关系:
java 复制代码
// 观察者接口
public interface Observer {
    void update(float temp, float humidity, float pressure);
}
// 主题接口
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
// 具体主题类(天气数据)
public class WeatherData implements Subject {
    private List observers = new ArrayList<>();
    private float temperature, humidity, pressure;
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }
    public void setMeasurements(float temp, float hum, float press) {
        temperature = temp;
        humidity = hum;
        pressure = press;
        notifyObservers();
    }
}

特点:灵活可控,但需自行处理线程安全和内存管理。


  1. 使用JDK内置类(ObservableObserver
    JDK提供的Observable类和Observer接口简化实现,但需注意其设计缺陷(如需手动调用setChanged()):
java 复制代码
// 具体主题类
public class WeatherData extends Observable {
    private float temperature, humidity, pressure;
    public void setMeasurements(float temp, float hum, float press) {
        temperature = temp;
        humidity = hum;
        pressure = press;
        setChanged();  // 标记状态变化
        notifyObservers(new MeasurementData(temp, hum, press)); // 推模型
    }
}
// 具体观察者类
public class CurrentConditionsDisplay implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof MeasurementData) {
            MeasurementData data = (MeasurementData) arg;
            System.out.println("温度:" + data.getTemp());
        }
    }
}

注意事项:

  • notifyObservers()需在setChanged()后调用,否则不触发通知。
  • 观察者需通过参数arg获取数据(推模型)或从主题拉取数据(拉模型)。

  1. 高级实现(异步与线程安全)
    通过线程池异步通知观察者,避免阻塞主题线程:
java 复制代码
public class AsyncSubject extends Subject {
    private ExecutorService executor = Executors.newFixedThreadPool(4);
    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            executor.submit(() -> o.update(...));
        }
    }
}

优势:提升并发性能,避免单线程阻塞。


二、测试方法

  1. 单元测试(Mockito框架)
    使用Mockito模拟观察者行为,验证通知逻辑:
java 复制代码
@Test
public void testObserverNotification() {
    // 1. 创建Mock观察者
    Observer mockObserver = Mockito.mock(Observer.class);
    
    // 2. 注册观察者到主题
    WeatherData weatherData = new WeatherData();
    weatherData.registerObserver(mockObserver);
    
    // 3. 触发状态变化
    weatherData.setMeasurements(25.5f, 65, 1013.1f);
    
    // 4. 验证观察者方法是否被调用
    Mockito.verify(mockObserver).update(25.5f, 65, 1013.1f);
}

关键点:

  • 使用when()设置Mock对象行为(如异常抛出)。
  • 使用verify()验证方法调用次数和参数。

  1. 集成测试(多线程场景)
    测试观察者模式在并发环境下的稳定性:
java 复制代码
@Test
public void testConcurrentObservers() {
    WeatherData weatherData = new WeatherData();
    Observer observer1 = new CurrentConditionsDisplay();
    Observer observer2 = new StatisticsDisplay();
    
    // 多线程注册观察者
    Thread t1 = new Thread(() -> weatherData.registerObserver(observer1));
    Thread t2 = new Thread(() -> weatherData.registerObserver(observer2));
    t1.start();
    t2.start();
    
    // 等待线程结束
    t1.join();
    t2.join();
    
    // 触发通知并验证
    weatherData.setMeasurements(30f, 70, 1012.5f);
    // 验证两个观察者均被通知
}

注意事项:

  • 使用synchronized或并发集合(如CopyOnWriteArrayList)保证线程安全。
  • 测试观察者是否因循环依赖导致死锁。

  1. 性能测试
    评估观察者数量对通知效率的影响:
java 复制代码
@Test
public void testObserverPerformance() {
    WeatherData weatherData = new WeatherData();
    int observerCount = 1000;
    for (int i = 0; i < observerCount; i++) {
        weatherData.registerObserver(new SimpleObserver());
    }
    
    long startTime = System.currentTimeMillis();
    weatherData.setMeasurements(25f, 60, 1013f);
    long endTime = System.currentTimeMillis();
    
    // 验证耗时是否在合理范围内
    Assert.assertTrue(endTime - startTime < 1000); // 1秒内完成
}

优化方向:

  • 使用异步通知减少阻塞。
  • 限制观察者数量或引入优先级队列。

三、常见问题与解决方案

  1. 内存泄漏:
    • 问题:未从主题移除观察者,导致无法被GC回收。
    • 解决方案:在观察者销毁时调用removeObserver(),或使用弱引用存储观察者。
  2. 循环依赖:
    • 问题:主题与观察者互相依赖,导致栈溢出。
    • 解决方案:通过中介者模式解耦。
  3. 线程安全:
    • 问题:多线程环境下注册/移除观察者时数据不一致。
    • 解决方案:使用synchronized或并发集合(如CopyOnWriteArrayList)。

四、总结

  • 实现选择:优先自定义实现以避免JDK类的缺陷,复杂场景可结合异步通知。
  • 测试重点:验证通知逻辑、线程安全及性能。
  • 工具推荐:Mockito用于单元测试,线程池和并发集合用于多线程场景。
相关推荐
_祝你今天愉快2 分钟前
HashMap 底层原理 (JDK 1.8 源码分析)
android·java·后端
七七软件开发5 分钟前
直播 app 系统架构分析
java·python·小程序·系统架构·php
Wendy144111 分钟前
【目标检测基础】——yolo学习
学习·yolo·目标检测
程序员陆通12 分钟前
Spring Cloud微服务中的内存泄漏问题定位与解决方案
java·spring cloud·微服务
极光雨雨14 分钟前
JVM中年轻代、老年代、永久代(或元空间)、Eden区和Survivor区概念介绍
java·jvm
盖世英雄酱5813626 分钟前
配置的那点玄学
java·后端
aiwery38 分钟前
深入理解React hooks:从设计初衷到自定义Hook指南
前端·设计模式
zyk_computer42 分钟前
Redis 实现互斥锁解决Redis击穿
java·数据库·redis·后端·缓存·性能优化·web
33255_40857_280591 小时前
ElasticSearch实战指南:从零部署到Java高效集成
java·elasticsearch
天上的光1 小时前
机器学习——学习路线
人工智能·学习·机器学习