设计模式之观察者模式
定义:
在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
大白话:
其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
模式原理
模式讲解:
- 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
- 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
- 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
- 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。
模式实例:
案例:实现天气预告功能,温度改变就通知所有订阅了天气预报公众号的用户。
- Java内置Api实现
创建一个被观察主题类:
package com.zte.service.test;
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
public WeatherData() {
}
/**
* 被观察者数据发生改变
* @param temperature
* @param humidity
* @param airpressure
*/
public void setMeasurements(float temperature) {
this.temperature = temperature;
this.measurementsChanged();
}
/**
* 修改后,通知观察者
*/
public void measurementsChanged() {
super.setChanged();
super.notifyObservers();
}
public float getTemperature() {
return temperature;
}
}
继承了Observable 类的主题类默认的具备了被观察者属性,通过调用setMeasurements方法自动会通知注册在该主题上的订阅者,原理我们往下看。
创建两个观察者类:
package com.zte.service.test;
import java.util.Observable;
import java.util.Observer;
public class WeatherDataObserver implements Observer {
// 定义观察者名称
private String name;
public String getObserverName() {
return name;
}
public void setObserverName(String observerName) {
this.name = observerName;
}
@Override
public void update(Observable o, Object arg) {
// 更新消息数据
System.out.println(name + "收到了发生变化的数据内容是:" + ((WeatherData) o).getTemperature());
}
}
package com.zte.service.test;
import java.util.Observable;
import java.util.Observer;
public class WeatherDataObserver2 implements Observer {
// 定义观察者名称
private String name02;
public String getObserverName() {
return name02;
}
public void setObserverName(String observerName) {
this.name02 = observerName;
}
@Override
public void update(Observable o, Object arg) {
// 更新消息数据
System.out.println(name02 + "收到了发生变化的数据内容是:" + ((WeatherData) o).getTemperature());
}
}
最后创建一个测试类:
package com.zte.service.test;
public class ObserverTest {
public static void main(String[] args) {
// 创建一个具体的被 观察者
WeatherData observable = new WeatherData();
// 创建第一个观察者
WeatherDataObserver one = new WeatherDataObserver();
one.setObserverName("我是观察者A");
// 创建第二个观察者
WeatherDataObserver2 two = new WeatherDataObserver2();
two.setObserverName("我是观察者B");
// 注册观察者
observable.addObserver(one);
observable.addObserver(two);
// 目标更新天气情况
observable.setMeasurements(10);
}
}
运行代码:
我是观察者B收到了发生变化的数据内容是:10.0
我是观察者A收到了发生变化的数据内容是:10.0
从结果我们可以知道当温度变化两个消息的订阅者都收到了消息通知,那么这是如何实现的呢?下面我们就自己动手来实现java内置的消息通知即观察者模式。
自定义观察者模式
1:首先我们要有一个观察的主题,既然是主题 肯定要知道哪些人订阅了我们的消息,哪些人退订了我们的消息,以及怎么样通知消息订阅者。
因此我们首先创建一个主题接口类:
/**
* 主题对象
*/
public interface Subject {
/**
* 添加观察者
* @param observer
*/
public void addObserver(Observer observer);
/**
* 删除指定观察者
* @param observer
*/
public void deleteObserver(Observer observer);
/**
* 通知所有观察者
*/
public void notifyObservers();
}
定义一个抽象观察接口
/***
* 抽象观察者
* 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。
* @author jstao
*
*/
public interface Observer {
public void update(String message);
}
定义被观察者,实现了Observerable接口,对Observerable接口的三个方法进行了具体实现,同时有一个List集合,用以保存注册的观察者,等需要通知观察者时,遍历该集合即可
package com.jstao.observer;
import java.util.ArrayList;
import java.util.List;
/**
* 被观察者,也就是微信公众号服务
* 实现了Observerable接口,对Observerable接口的三个方法进行了具体实现
* @author jstao
*
*/
public class WeatherData implements Observerable {
//温度
private float temperature;
//观察者列表
private ArrayList<Observer> observerArrayList;
public WeatherData() {
this.observerArrayList = new ArrayList<Observer>();
}
/**
* 添加指定观察者对象
* @param observer
*/
@Override
public void addObserver(Observer observer) {
this.observerArrayList.add(observer);
}
/**
* 删除指定观察者对象
* @param observer
*/
@Override
public void deleteObserver(Observer observer) {
int i;
if((i = observerArrayList.indexOf(observer)) != -1) {
this.observerArrayList.remove(i);
}
}
/**
* 通知观察者
*/
@Override
public void notifyObservers() {
for(Observer observer : this.observerArrayList) {
observer.update(this.temperature, this.humidity, this.airpressure);
}
}
/**
* 被观察者数据发生改变
* @param temperature
* @param humidity
* @param airpressure
*/
public void setMeasurements(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.measurementsChanged();
}
/**
* 修改后,通知观察者
*/
public void measurementsChanged() {
this.notifyObservers();
}
}
定义两个观察者对象:观察者对象,在初始化构造的时候,注册主题(成为观察主题的对象)
public class CurrentConditionsDisplay implements Observer{
private float temperature;
private Observerable subject;
public CurrentConditionsDisplay(Observerable subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void update(float temperature) {
this.temperature = temperature;
}
public class CurrentConditionsDisplay1 implements Observer{
private float temperature;
private Observerable subject;
public CurrentConditionsDisplay1(Observerable subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void update(float temperature) {
this.temperature = temperature;
}
编写测试类:
public class WeatherMain {
public static void main(String[] args) {
//被观察者
WeatherData weatherData = new WeatherData();
//观察者对象
CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
CurrentConditionsDisplay1 statisticsDisplay = new CurrentConditionsDisplay1 (weatherData);
weatherData.setMeasurements(80);
}
测试方法同上。 和自定义观察者模式的区别在于,通知观察者前首先要调用setChanged()方法,修改主题对象的标识,该标识的好处是可以自由的控制主题对象和观察者之间的交互,比如一些微小的数据修改不通知观察者,就可以通过该方法来控制;还有观察者update(Observable o, Object arg)接口的第一个参数Observable o是可以让观察者知道是哪个主题调用它,也可以通过该主题对象,去主题拉取数据。Object arg参数是主题对象推送过来的数据对象,观察者直接可以获取主题对象发送的数据进行处理。
经过测试我们可以观察到,通过Java内置的观察者模式,我们发现Observalbe的notifyObservers()方法通知观察者的时候和我们自定义观察者模式的顺序是不一样的,但如果我们的代码依赖这样的次序,这种实现就是错的。通知次序的改变,很可能会产生错误的结果。
另一方面,Observable是一个类,并不是一个接口,它限制了我们的复用,也违反了我们的OO设计原则(针对接口编程,而非针对实现编程)。如果主题对象要想继承其他超类,就会陷入两难的境地,毕竟Java不支持多继承,限制了Observable的复用潜力。再者,因为没有Observable接口,所以也无法建立自己的实现,和Java内置Observer API搭配使用。而且也违反了我们的设计原则:多用组合,少用继承。
所以不管用哪种方法都可以实现观察者模式,前提是根据我们的业务场景来。