目录
认识观察者模式
- 报社的业务之一就是出版报纸
- 向某家报社订阅报纸,只要他们有新报纸出版,就是给你送来,只要你是报社的订阅者,你就会一直受到报纸
- 当你不想再看报纸的时候,取消订阅,报社就不会再送新报纸来
- 只要有人向报社订阅报纸或取消报纸,报社就还在经营
其实观察者模式就如同这报纸的订阅一样,有出版者也有订阅者。
主题+观察者
观察者模式的出版者就是“主题(Topic)”,它的订阅者就是“观察者(Observer)”
接下来我们看看下面这张图:
定义观察者模式
观察者模式定义了一系列对象之间一对多的依赖关系,当一个主题对象改变状态时,其他依赖者都会收到通知。
目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。
接下来我们看看下面这张类图:
实例
首先我们来看一下Subject这个主题接口
public interface Subject {
//这两个方法都需要一个观察者作为变量,该观察者是用来注册或删除的
public void registerObserver(MyObserver o);
public void removeObserver(MyObserver o);
//当主题状态改变时,这个方法会被调用,以通知所有注册的观察者
public void notifyObserver();
}
接下来我们看一下Observer这个观察者接口
public interface MyObserver {
//所有观察者都必须实update方法,以实现观察者接口
public void update(float temp,float humidity,float pressure);
}
然后我们为天气这个具体的主题来实现Subject接口
public class WeatherDataSubjectV1 implements Subject {
private List<MyObserver> list;
private float temp;
private float humidity;
private float pressure;
public WeatherDataSubjectV1() {
list = new ArrayList<MyObserver>();
}
@Override
public void registerObserver(MyObserver o) {
list.add(o);
}
@Override
public void removeObserver(MyObserver o) {
if(!list.isEmpty())
list.remove(o);
}
//在这里把主题的状态告诉每一个观察者
@Override
public void notifyObserver() {
for(int i = 0; i < list.size(); i++) {
MyObserver oserver = list.get(i);
oserver.update(temp,humidity,pressure);
}
}
//主题状态更新是通知观察者
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temp,float humidity,float pressure) {
this.temp=temp;
this.pressure=pressure;
this.humidity=humidity;
measurementsChanged();
}
}
既然我们已经有了具体的主题,那就来看看这个主题的观察者吧
public class WeatherObserverV1 implements MyObserver{
private float temp;
private float humidity;
private Subject weatherData;
public WeatherObserverV1(Subject weatherData) {
//向主题进行注册
this.weatherData=weatherData;
weatherData.registerObserver(this);
}
public void display() {
System.out.println("Current conditions:" + temp+"F degrees and "+humidity+"%humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp=temp;
this.humidity=humidity;
display();
}
}
最后我们来测试一下到底这个观察者能不能收到天气主题的更新动态
public class test {
public static void main(String[] args) {
System.out.println("------Version 1------------------------------------");
WeatherDataSubjectV1 weatherSubV1=new WeatherDataSubjectV1();
MyObserver weatherObs=new WeatherObserverV1(weatherSubV1);
weatherSubV1.setMeasurements(80, 75, 30.4f);
System.out.println("温度发生了变化>>>>>");
weatherSubV1.setMeasurements(81, 75, 30.4f);
System.out.println("湿度发生了变化>>>>>");
weatherSubV1.setMeasurements(81, 77, 30.6f);
}
}
来看一下控制台输出的结果
------Version 1------------------------------------
Current conditions:80.0F degrees and 75.0%humidity
温度发生了变化>>>>>
Current conditions:81.0F degrees and 75.0%humidity
湿度发生了变化>>>>>
Current conditions:81.0F degrees and 77.0%humidity
看吧,观察者收到了主题更新的通知了,但是。。。是不是觉得主题有些强势,不管观察者是否想要这些数据都会收到主题通知的全部信息。
那有没有办法让观察者只得到自己想要的数据呢?
Java内置的观察者模式如何运作?
其实在java.util包中包含了Observer接口和Observable类,这和我们上面的Subject接口和MyObserver接口很相似。Observer接口和Observable类使用上更方便,可以使用推(push)或拉(pull)的方式传送数据。
1:如何把对象变成观察者
实现观察者接口(java.util.Observer),然后调用Observable对象的addObserver()方法,不想再当观察者时,调用deteleObserver()方法就可以了
2:可观察者(也就是指主题)要如何送出通知
首先需要利用继承java.util.Observable产生“可观察者”类
先调用setChanged()方法,标记状态已经改变
然后调用两种notifyObservers()方法中的一个:notifyObservers()或notifyObservers(Object arg)
3:观察者如何接受通知
同之前一样,观察者实现了更新的方法,但是方法的参数不太一样:
//Observable o 主题对象,让观察者知道是哪个主题通知它的
//Object arg 传入notifyObservers方法中的数据对象,如果没有则说明为空
update(Observable o,Object arg)
如果你想push数据给观察者,你可以把数据当做args传送给notifyObservers(Object arg),否则,观察者就必须从可观察者对象中pull数据,那如何pull数据呢?
下面我们来看一下通过Observer接口和Observable类如何实现天气的更新通知
首先把WeatherObserver改成继承java.util.Observable类
public class WeatherDataSubjectV2 extends Observable {
private float temp;
private float humidity;
private float pressure;
public WeatherDataSubjectV2() {}
public float getTemp() {
return temp;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public void setMeasurements(float temp,float humidity,float pressure) {
this.temp=temp;
this.pressure=pressure;
this.humidity=humidity;
measurementsChanged();
}
public void measurementsChanged() {
//在调用notifyObservers之前,要先调用setChanged()来指示状态已经改变
setChanged();
notifyObservers();
}
}
现在我们来看看新的观察者要怎么做
public class WeatherObserverV2 implements Observer{
private float temp;
private float humidity;
private Observable observable;
public WeatherObserverV2(Observable observable) {
this.observable=observable;
observable.addObserver(this);//登记成为观察者
}
public void display() {
System.out.println("Current conditions:" + temp+"F degrees and "+humidity+"%humidity");
}
/*
* 在update()方法中,先确定观察者属于WeatherDataSubjectV2类型,然后利用getter方法取值
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherDataSubjectV2) {
WeatherDataSubjectV2 v2=(WeatherDataSubjectV2) obs;
this.temp=v2.getTemp();
this.humidity=v2.getHumidity();
display();
}
}
}
最后来测试一下观察者是否获取得到数据吗
public class test {
public static void main(String[] args) {
System.out.println("------Version 2-------------------------------------");
WeatherDataSubjectV2 weatherSubV2=new WeatherDataSubjectV2();
WeatherObserverV2 weatherObsV2=new WeatherObserverV2(weatherSubV2);
weatherSubV2.setMeasurements(80, 75, 30.4f);
System.out.println("温度发生了变化>>>>>");
weatherSubV2.setMeasurements(81, 75, 30.4f);
System.out.println("湿度发生了变化>>>>>");
weatherSubV2.setMeasurements(81, 77, 30.6f);
}
}
好了,我们来看一下结果
------Version 2-------------------------------------
Current conditions:80.0F degrees and 75.0%humidity
温度发生了变化>>>>>
Current conditions:81.0F degrees and 75.0%humidity
湿度发生了变化>>>>>
Current conditions:81.0F degrees and 77.0%humidity
大家是否注意到一个问题,在调用notifyObservers之前,为什么要先调用setChanged()
让我们来看看Observable这个类的内部:
setChanged(){
changed=true;
}
notifyObservers(Object arg){
if(changed){
for every observer on this list{
call update(ths,arg);
}
}
}
notifyObservers(){
notifyObservers(null);
}
发布+订阅=观察者模式???
来看一下这张图:
从上面的图我们可以发现:发布+订阅≠观察者模式
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。