一、实现方式
- 自定义实现
通过手动定义Subject
和Observer
接口,实现一对多依赖关系:
// 观察者接口
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();
}
}
特点:灵活可控,但需自行处理线程安全和内存管理。
- 使用JDK内置类(
Observable
与Observer
)
JDK提供的Observable
类和Observer
接口简化实现,但需注意其设计缺陷(如需手动调用setChanged()
):
// 具体主题类
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
获取数据(推模型)或从主题拉取数据(拉模型)。
- 高级实现(异步与线程安全)
通过线程池异步通知观察者,避免阻塞主题线程:
public class AsyncSubject extends Subject {
private ExecutorService executor = Executors.newFixedThreadPool(4);
@Override
public void notifyObservers() {
for (Observer o : observers) {
executor.submit(() -> o.update(...));
}
}
}
优势:提升并发性能,避免单线程阻塞。
二、测试方法
- 单元测试(Mockito框架)
使用Mockito模拟观察者行为,验证通知逻辑:
@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()
验证方法调用次数和参数。
- 集成测试(多线程场景)
测试观察者模式在并发环境下的稳定性:
@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
)保证线程安全。 - 测试观察者是否因循环依赖导致死锁。
- 性能测试
评估观察者数量对通知效率的影响:
@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秒内完成
}
优化方向:
- 使用异步通知减少阻塞。
- 限制观察者数量或引入优先级队列。
三、常见问题与解决方案
- 内存泄漏:
- 问题:未从主题移除观察者,导致无法被GC回收。
- 解决方案:在观察者销毁时调用
removeObserver()
,或使用弱引用存储观察者。
- 循环依赖:
- 问题:主题与观察者互相依赖,导致栈溢出。
- 解决方案:通过中介者模式解耦。
- 线程安全:
- 问题:多线程环境下注册/移除观察者时数据不一致。
- 解决方案:使用
synchronized
或并发集合(如CopyOnWriteArrayList
)。
四、总结
- 实现选择:优先自定义实现以避免JDK类的缺陷,复杂场景可结合异步通知。
- 测试重点:验证通知逻辑、线程安全及性能。
- 工具推荐:Mockito用于单元测试,线程池和并发集合用于多线程场景。