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用于单元测试,线程池和并发集合用于多线程场景。
相关推荐
Maybyy3 分钟前
力扣454.四数相加Ⅱ
java·算法·leetcode
逼子格6 分钟前
权电阻网络DAC实现电压输出型数模转换Multisim电路仿真——硬件工程师笔记
笔记·嵌入式硬件·硬件工程·硬件工程师·adc·硬件工程师真题·权电阻网络dac
Java中文社群6 分钟前
面试官:谈谈你AI项目的具体实现?
java·后端·面试
Jyywww12114 分钟前
慕尚花坊项目笔记
笔记
xd0000227 分钟前
ethers.js-5–和solidity的关系
笔记
找了一圈尾巴36 分钟前
设计模式(结构型)-适配器模式
设计模式·适配器模式
哪里不会点哪里.38 分钟前
适配器模式:兼容不兼容接口
java·开发语言
vvilkim41 分钟前
单例模式详解:确保一个类只有一个实例
单例模式·设计模式
何中应43 分钟前
Maven项目没有Maven工具,IDEA没有识别到该项目是Maven项目怎么办?
java·后端·maven·intellij-idea
neoooo43 分钟前
Redis锁得住,世界就是你的:一探Redis分布式锁的原理、姿势与深度思考
java·redis·后端