文章目录
一、观察者模式(行为型模式)
1.1 场景
有这么个项目需求,直接使用尚硅谷的例子(同理的也可以使用什么新闻中心,发布消息这样的例子)
1.2 普通解决方案
不用设计模式有个方案简单说下就是气象站提供各种获取的接口,当有数据更新,气象站会更新数据(或者推送给其他网站),之后通过getXX获取的数据都是最新的。
简单来说呢,就是一个数据源(气象站),一个需要获取数据的网站(就当是小米天气),气象站会引入小米天气,在数据更新的时候,去调用小米天气的方法把最新的信息推送给他。
这样的方法问题是
- 当使用气象局的第三方网站越来越多,需要引入的网站实例(需要创建对应网站的对象)也就越来越多
- 无法动态添加第三方(因为是代码里写死引入的)
- 违反OCP原则(对扩展开发,对修改关闭)
1.3 观察者模式定义
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于 它的对象都会得到通知并被自动更新。
- Subject 被观察者
- 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽 象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观 察者。
- Observer 观察者
- 观察者接收到消息后,即进行 update(更新方法)操作,对接收到的信息进行处 理。
- ConcreteSubject 具体的被观察者
- 定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
- ConcreteObserver 具体的观察者
- 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
1.4 观察者模式原理
我们的气象局,或者说报社,可以说是被观察者
他有对应的方法,比如报社,需要知道哪些人订了报纸,订了也可以退订,当数据更新(有新报纸)了,就采取对应的通知方法(报社就是到了发短信,气象局就推送新的气象信息)
气象站/报社和用户之间的关系,正常来说是一对多,一个报社或者气象站会对应多个用户
从这里我们看到了类似于装饰者模式的4个角色
- 被观察者抽象,定义了管理观察者的方法
- 被观察者实现,具体的数据源,组合观察者列表
- 观察者抽象: 定义了观察者(订阅者)获取信息后处理的方法
- 观察者实现: 各个观察者实现观察者接口,通过接口定义的方法传入的信息,获取各自所需的值(可以少获取其实就是获取了不处理)
二、代码实现
2.1 代码结构
这里研究的是对象一(被观察者)对多(观察者)的关系
2.2 上代码
1.被观察者接口,这里是Subject
/**
* 注册观察者到观察者列表
* @param observer
*/
public void registerObserver(Observer observer);
/**
* 移除观察者从观察者列表
* @param observer
*/
public void removeObserver(Observer observer);
/**
* 通知所有观察者列表
*/
public void notifyObservers();
2.被观察者实现,这里是天气对应的气象站,也可以是报纸订阅的报社
/**
* 被观察者数据源具体实现 - 这里可以理解为气象站
*/
@Data
public class WeatherData implements Subject {
// 气象站提供的信息
// 温度
private double temperature;
// 气压
private double presure;
// 湿度
private double humidity;
// 观察者(订阅)列表
private List<Observer> observers;
// 构造方法初始化列表 - 组合
public WeatherData(){
observers = new ArrayList<>();
}
/**
* 获取到最新的数据,设置并通知订阅者列表
* @param temperature
* @param presure
* @param humidity
*/
public void setData(double temperature, double presure, double humidity) {
this.temperature = temperature;
this.presure = presure;
this.humidity = humidity;
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if (observers.contains(observer)){
observers.remove(observer);
}
}
@Override
public void notifyObservers() {
for (Observer observer: observers){
observer.update(getTemperature(),getPresure(),getHumidity());
}
}
}
3.观察者接口,这里定义观察者获取到数据后的操作方法
/**
* 观察者抽象
*/
public interface Observer {
public void update(double temperature, double presure, double humidity);
}
4.观察者实现,这里是具体调用数据的站点(应用),只列举一个 - 小米
通过这张图,我们知道会有多个订阅者,我们这里只实现一个(别的基本一样,就是输出前缀不一)
(可以不建立对应成员变量,直接使用实现方法参数传来的数据进行处理 - 详情见扩展性)
/**
* 具体的观察者实现
*/
public class XiaoMiObserver implements Observer {
private double temperature;
private double presure;
private double humidity;
@Override
public void update(double temperature, double presure, double humidity) {
// 这里实现了观察者接口方法,并执行了获取数据后的操作 -打印
this.temperature = temperature;
this.presure = presure;
this.humidity = humidity;
display();
}
public void display(){
System.out.println(String.format("[小米天气]今天的温度为%s, 气压:%s,湿度是%s", temperature, presure, humidity));
}
}
5.准备完成后,就可以气象站工作
public class Client {
public static void main(String[] args) {
// 观察者模式下,模拟气象站新增去除订阅者
// 先注册一个数据源(被观察者)
WeatherData weatherData = new WeatherData();
// 创建一个数据源使用方(观察者)
XiaoMiObserver xiaoMiObserver = new XiaoMiObserver();
// 调用方注册到数据源
weatherData.registerObserver(xiaoMiObserver);
// 更新数据,会通知到小米(目前只有小米)
System.out.println("信息更新.通知订阅方!");
weatherData.setData(10.2,28,36);
// 新增一个观察者,只需要建立对应的实体类实现对应方法
HeFengObserver heFengObserver = new HeFengObserver();
weatherData.registerObserver(heFengObserver);
// 这时候数据更新会通知小米、和风
System.out.println("信息更新.通知订阅方!");
weatherData.setData(15.2,17.9,3.26);
// 跟小米解除订阅关系 - 下次信息推送就不会有小米了
weatherData.removeObserver(xiaoMiObserver);
System.out.println("信息更新.通知订阅方!");
weatherData.setData(55,45,25);
}
}
运行结果
信息更新.通知订阅方!
[小米天气]今天的温度为10.2, 气压:28.0,湿度是36.0
信息更新.通知订阅方!
[小米天气]今天的温度为15.2, 气压:17.9,湿度是3.26
[和风天气]今天的温度为15.2, 气压:17.9, 湿度:3.26
信息更新.通知订阅方!
[和风天气]今天的温度为55.0, 气压:45.0, 湿度:25.0
2.3 扩展性
在装饰者模式下,我们需要新增一个合作厂商(观察者),只需要实现观察者接口,实现对应方法,不会对原先的调用产生影响,扩展性良好
1.新增具体观察者**(可以不建立对应成员变量,直接使用实现方法参数传来的数据进行处理)**
/**
* 扩展实现 - 新增观察者 - 彩云天气
*/
public class CaiYunObserver implements Observer {
@Override
public void update(double temperature, double presure, double humidity) {
System.out.println(String.format("[彩云天气]今天的温度为:%s, 气压: %s, 湿度:%s",temperature,presure, humidity));
}
}
2.调用
// 在原先跟小米接触合作后,接上跟彩云天气建立合作
CaiYunObserver caiYunObserver = new CaiYunObserver();
weatherData.registerObserver(caiYunObserver);
System.out.println("信息更新.通知订阅方!");
weatherData.setData(55,27,0);
结果
三、 框架应用
经过了解,我们知道,观察者模式应用在了Java Observable的设计中,略微不同的是,在Observable中,他没有将Subject(被观察者)抽象,而是直接在类中实现, 也是引入了一个观察者列表(obs)来维护
我们可以看到,对应的新增删除通知方法都有
我们通过读取源码确定了
- Observable 作为被观察者(无接口直接实现)
- Observer 作为观察者接口
但是Observable 在JDK9就被放弃了
因为:
- 不可序列化-因为,Observable不实现可序列化,也不能对其子类进行序列化。
- 没有线程安全-方法可以被其子类覆盖,并且事件通知可以以不同的顺序发生,并且可能在不同的线程上发生,这足以破坏任何“线程安全”。
- 为了在线程之间进行可靠且有序的消息传递,请考虑使用java.util.concurrent程序包中的并发数据结构之一
四、小结
- 使用观察者模式设计后,会以集合的方式管理用户(Observer),包括注册,移除和通知。
- 这样新增观察者不需要修改气象站(被观察者)的代码,遵守了OCP原则。
- 我们在实现像QQ和微信公众号推送消息这种场景就可以使用观察者模式。