观察者模式
一 概述
什么是观察者模式
观察者模式又名发布-订阅模式,具体概念就是定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖的对象都得到通知并被自动更新。
为什么要用观察者模式
举个例子,咱们的天气是时刻变化的,而气象局要监控这些变化,实时获取到最新的天气数据。
如何实时监控数据的变化呢?
- 可以一直去发送请求去询问是否发生变化,比如每隔一秒去询问一遍,这样虽然可以获取到数据的变化,但是不能保证是最新的。假如你这一秒去询问发现数据没有发生变化,但是在0.1S后数据发生变化,你只有在下一秒才能知道这次的数据变化,那么说明你的询问频率还不够快。
- 而且这只是一个气象局在询问,如果各地的气象局都需要知道这个气象数据,就会产生大量的询问请求,听起来就很烦
所以主动者就应该放在天气数据这边也就是发布者,发生改变就通知所有的订阅者进行数据的更新,这就是我理解的为什么使用观察者模式。
二 观察者模式的结构与使用
角色
观察者模式中分为4个角色:
- 主题(IObservable):主题是一个接口,规定了具体主题需要实现的方法,如,添加,删除观察者,通知观察者更新数据的方法。
- 观察者(Observer):也是一个接口,规定了具体观察者用来更新数据的方法
- 具体主题(ConcreteObservable):实现主题接口的一个实例,除了实现接口的方法还要有一个集合,存放观察者,以便数据变化时通知观察者。
- 具体观察者(ConcreteObserver):是观察者接口的一个实例,除了观察者中的方法,还要有主题接口的变量,方便具体主题将自己添加到集合或者删除。
具体实现
我们这里就假设有北京和山西两地的气象局要收集一个气象站的信息,用发布订阅来实现:
- 主题
//发布者(被观察者)接口
public interface IObservable {
//注册观察者
public void addObserver(IObserver o);
//删除观察者
public void deleteObserver(IObserver o);
//通知观察者
public void notifyObservers();
}
- 观察者
//订阅者(观察者)接口
public interface IObserver {
//具体观察者用来更新数据的方法
public void update(double temperature,double humidity);
}
- 具体主题
public class Weather implements IObservable{
//温度
private double temperature;
//湿度
private double humidity;
//存放观察者
private ArrayList<IObserver> cObserver;
public Weather(){
cObserver = new ArrayList<>();
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public void setDate(double temperature,double humidity){
this.humidity = humidity;
this.temperature = temperature;
dataChange();
}
//数据产生变化后通知观察者
public void dataChange(){
notifyObservers();
}
//添加观察者
@Override
public void addObserver(IObserver o) {
cObserver.add(o);
}
//删除观察者
@Override
public void deleteObserver(IObserver o) {
if(cObserver.contains(o))
cObserver.remove(o);
else
System.out.println("对象不存在");
}
//通知观察者
@Override
public void notifyObservers() {
for(int i = 0,len = cObserver.size();i<len;i++){
cObserver.get(i).update(getTemperature(), getHumidity());
}
}
}
- 具体观察者
public class SxStation implements IObserver{
private double temperature;
private double humidity;
@Override
public void update(double temperature, double humidity) {
// TODO Auto-generated method stub
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display(){
System.out.println("山西气象局报道 今天温度:"+temperature+";湿度:"+humidity);
}
}
public class BjStation implements IObserver{
//温度
private double temperature;
//湿度
private double humidity;
//具体的更行数据的
@Override
public void update(double temperature, double humidity) {
// TODO Auto-generated method stub
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display(){
System.out.println("北京气象局报道 今天温度:"+temperature+";湿度:"+humidity);
}
}
- main方法
public class MainClass {
public static void main(String[] args) {
Weather w = new Weather();
IObserver s = new SxStation();
IObserver b = new BjStation();
//将观察者注册到被观察者中
w.addObserver(s);
w.addObserver(b);
//更新数据
w.setDate(10.20, 10.20);
}
}
结果
这就是简单的观察者模式的一个实现
关于拉数据与推数据
- 推数据:是指具体主题发生变化后的数据全部交给具体观察者,即将变化后的数据传递给具体观察者用于更新数据方法的参数。当具体主题认为具体观察者需要这些变换后的全部数据时往往采用推数据的方式
- 拉数据:指具体主题不将变化后的数据交给具体观察者,而是提供了获取这些数据的方法,比如get方法,只提供了通知方法。具体观察者得到通知后调用获取数据方法得到自己想要的数据。
上面的例子采用的是推数据的方法,如果感兴趣,大伙可以自己尝试改编一下变成拉数据的方式。比如:山西报道温度,北京报道湿度
Java API中的Observable类与Observer接口
java中在java.util包提供了用来设计符合观察者模式的Observable类与Observer接口。但需要注意的是Observable是一个类,他的子类不能再继承其他的类了,而且Observable类中没有使用集合,会产生一个警告,但不影响运行,感兴趣的小伙伴可以更具文档实验一下。
总结
- 优缺点
1、观察者模式的优点
观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。
2、观察者模式的缺点
在应用观察者模式时需要考虑一下开发小路问题,程序中包括一个被观察者和多个被观察者,开发和调试比较复杂,而且Java中的消息的通知默认是顺序执行的,一个观察者的卡顿会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。 - 适用场景
1、当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2、当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要被改变。
3、当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,不希望这些对象是紧密耦合的。
这就是我所理解的观察者模式,如果有什么问题可以纠正一下,毕竟我也刚刚学习完。