设计模式-总览https://mp.csdn.net/mp_blog/creation/editor/122202507 来自《Head First设计模式》的例子为发布订阅天气数据,当天气数据更新时自动更新面板上的天气数据。
个人理解:观察者模式可以理解为事物之间存在一对一或者一对多的关系,当事物存在变化时需要通知其他关联事物发生变化。也可以理解成发布订阅模式,类似于消息可以进行系统解耦;只是消息可以理解成分布式的解耦(即跨进程的解耦),而该观察者模式为进程内部的发布订阅关系。
一、自己实现观察者模式
1、发布端
1)、定义主题(一些动作接口:订阅、取消订阅、变更通知)
/**
* 观察者模式的订阅接口
*
* @author lihongmin
* @date 2018/9/1 14:45
*/
public interface Subject {
/**
* 订阅(注册观察)
*
* @param observer 观察者对象
* @return 是否订阅成功
*/
boolean registerObserver(Observer observer);
/**
* 退订(取消观察)
*
* @param observer 观察者对象
* @return 是否取消成功
*/
boolean removeObserver(Observer observer);
/**
* 观察变更的通知接口
*/
void notifyObservers();
}
2)、主题的实现(一个容器存放观察者列表,实现主题接口)
/**
* 观察者模式的实现
* @author lihongmin
* @date 2018/9/1 15:08
*/
public class WeatherData implements Subject {
/**
* 所有的观察者
*/
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList<>();
System.out.println("初始化了订阅者列表容器!");
}
@Override
public boolean registerObserver(Observer observer) {
observers.add(observer);
System.out.println("有一个订阅者订阅了我们的变更通知!");
return true;
}
@Override
public boolean removeObserver(Observer observer) {
if (observers.contains(observer)) {
System.out.println("有一个订阅者取消订阅了!");
observers.remove(observer);
return true;
}
return false;
}
@Override
public void notifyObservers() {
System.out.println("有主题变更了,我要安装注册列表挨个通知他们,真麻烦!");
// 便利,并变更通知
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void onChanged() {
notifyObservers();
}
public void setChangeParam(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
System.out.println("收到数据变化了,先赋值再通知吧!");
onChanged();
}
}
2、观察者
1)、观察者定义(一个连接主题和观察者的线)
/**
* 观察者对象定义
*
* @author lihongmin
* @date 2018/9/1 14:51
*/
public interface Observer {
/**
* 订阅变更通知定义,当发生变更时会用参数的方式通知(下发具体实现)
*
* @param temperature 变更的温度
* @param humidity 变更的湿度
* @param pressure 变更的。。
*/
void update(float temperature, float humidity, float pressure);
}
2)、观察到主题变更的对应动作(更新布告板天气内容)
/**
* 收到变更需要做的事情
*
* @author lihongmin
* @date 2018/9/1 14:56
*/
public interface DisplayElement {
/**
* 更换显示
*/
void display();
}
3)、观察动作的实现(布告板实现)
有了主题、有了主题订阅者的存储容器;有了观察者定义,有了观察到变更后的反映动作;那么现在需要的是将观察者和动作进行关联,所以该类实现了两个接口。
/**
* 布告板实现
*
* @author lihongmin
* @date 2018/9/1 15:22
*/
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject subject;
public CurrentConditionsDisplay(Subject subject) {
// 初始化
this.subject = subject;
// 将自己注册到观察者通知列表
System.out.println("我将自己注册到观察者通知列表!");
subject.registerObserver(this);
}
@Override
public void display() {
System.out.println("现在变更布告板了, 现在的温度为: " + temperature +
" ,现在的湿度为:" + humidity);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
System.out.println("哈哈,订阅了那么久,我收到变更通知了!");
display();
}
}
3、模拟发布订阅
public class TestObserver {
public static void main(String[] args) {
System.out.println("初始化了天气实现!");
WeatherData weatherData = new WeatherData();
System.out.println("初始化了需要订阅的其中一个气象显示板(我只是其中之一)!");
CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);
System.out.println(" 气象变更了");
weatherData.setChangeParam(20.5F, 10.5F, 24);
}
}
初始化了天气实现!
初始化了订阅者列表容器!
初始化了需要订阅的其中一个气象显示板(我只是其中之一)!
我将自己注册到观察者通知列表!
有一个订阅者订阅了我们的变更通知!
气象变更了
收到数据变化了,先赋值再通知吧!
有主题变更了,我要安装注册列表挨个通知他们,真麻烦!
哈哈,订阅了那么久,我收到变更通知了!
现在变更布告板了, 现在的温度为: 20.5 ,现在的湿度为:10.5
分析:看似很简单的几个类,但是却感觉关系非常的复杂。有一个主题,有一个(或多个)观察者,那么肯定需要一个地方存储他们的关系(谁或者哪几个订阅了一个什么主题,谁有订阅了另一个什么主题),当然期间可以进行解绑;主题变更了怎么让观察者知道,肯定需要预留一个调用的方法;这个方法会触发什么后续订阅者的动作,定义一个接口。最后还有一个地方,让预留接口和后续动作进行关联,关联的只是接口,最终实现真正需要做的事。
后续画一个时序图吧,,,
二、Jdk的观察者模式
github地址为:https://github.com/kevin-lihongmin/designpattern/tree/master/src/main/java/com/kevin/designpattern/headfirst/observer/jdk,知道了上面自己完全的写一个观察者模式的发布订阅流程,发现还是需要有很多的对象和关联动作,jdk对这些进行了封装,提供了主题和订阅者两个接口。
Observable(可观察的,那不就相当于是主题):
使用 Vector容器来保存订阅者,保证了并发线程的安全。
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
// ......
}
Observer(观察者):与上面的一致,只需要定义变更的预留接口,回调时会告知主题和主题的订阅者列表
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
1、主题定义
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
}
public WeatherData(float temperature, float humidity) {
System.out.println("数据变化,设置了值,可以让观察者以拉取的方式获取数据哦!");
this.temperature = temperature;
this.humidity = humidity;
}
/**
* 变更时调用父类方法
* 1、{@link Observable#setChanged()} 修改状态
* 2、{@link Observable#notifyObservers()} 采用拉取的方式
*/
public void onChanged() {
// 调用父类Observable的方法
System.out.println("将Observable的变更标示更改了,并通知观察者来拉取数据了!");
setChanged();
notifyObservers();
}
/**
* 为观察者提供拉取{@link WeatherData#temperature}的方法
*
* @return 温度数据
*/
public float getTemperature() {
System.out.println("观察者拉取了温度数据!");
return temperature;
}
/**
* 为观察者提供拉取{@link WeatherData#humidity}的方法
*
* @return
*/
public float getHumidity() {
System.out.println("观察者拉取了湿度数据!");
return humidity;
}
/**
* 为观察者提供拉取{@link WeatherData#pressure}的方法
*
* @return
*/
public float getPressure() {
return pressure;
}
}
2、主题变更后的接连动作
/**
* 收到变更需要做的事情
*
* @author lihongmin
* @date 2018/9/1 14:56
*/
public interface DisplayElement {
/**
* 更换显示
*/
void display();
}
3、主题订阅的实现
/**
*
* @author lihongmin
* @date 2018/9/1 16:13
*
* @see Observer
* @see Observable
*/
public class CurrentConditionsDisplay implements Observer,DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
// 将自己进行注册
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("现在变更布告板了, 现在的温度为: " + temperature +
" ,现在的湿度为:" + humidity);
}
@Override
public void update(Observable obs, Object arg) {
System.out.println("收到数据变更了!");
if (obs instanceof WeatherData) {
System.out.println("原来是我订阅的WeatherData数据,我还订阅了很多种类型的数据哦!");
WeatherData weatherData = WeatherData.class.cast(obs);
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
System.out.println("拿到数据了,通知变更显示了!");
display();
}
}
}
4、演示发布订阅流程
public class TestJdkObserver {
public static void main(String[] args) {
System.out.println("主题变更了,我需要new一个对象 (设置变更的温度湿度等...)! ");
WeatherData weatherData = new WeatherData(20.5F, 10.5F);
CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);
System.out.println("准备好了,通知变更,哈哈哈! ");
weatherData.onChanged();
}
}
主题变更了,我需要new一个对象 (设置变更的温度湿度等...)!
数据变化,设置了值,可以让观察者以拉取的方式获取数据哦!
准备好了,通知变更,哈哈哈!
将Observable的变更标示更改了,并通知观察者来拉取数据了!
收到数据变更了!
原来是我订阅的WeatherData数据,我还订阅了很多种类型的数据哦!
观察者拉取了温度数据!
观察者拉取了湿度数据!
拿到数据了,通知变更显示了!
现在变更布告板了, 现在的温度为: 20.5 ,现在的湿度为:10.5
总结:jdk抽取了发布订阅的观察者模式中公共的部分后,我们实现观察者模式还需要做的事,如下:
1、定义主题,继承自Observable,则一个主题会有一个Vector容器存在自己订阅的观察者,并且该主题还需要提供一个触发变更的地方,该地方需要调用jdk的Observer的update接口,告诉他变更的主题和主题的订阅者列表
2、变更后的相关动作,(当然这个接口可以不用定义,在下面直接调用实现)
3、订阅者实现类,实现Observer的update接口,让主题变更时调用update,多态的调用到实现的方法,该方法也就是真正监听变化后的后续动作
4、一切准备就绪,就需要去调用主题预留的变更方法