假设有一个服务可供订阅:天气预报服务
每个系统都可以订阅这个服务,天气预报过一段时间就更新信息,一旦更新也要把更新后的信息推送给各个订阅者
观察者模式的作用有点像消息队列
这大概就是观察者模式的主要作用,信息一更新马上就给所有观察者推送
Java原生手写写法
先创建一个相当于注册中心的接口,对观察者进行操作
public interface Subject {
/**
* 添加一个观察者
* @param observer
*/
void registerObserver(Observer observer);
/**
* 移除一个观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 对所有观察者进行通知
*/
void notifyObservers();
}
定义一个观察者接口,因为举例和天气有关,所以提供一个更新方法,每个观察者订阅后都可以更新自己系统显示的天气信息
public interface Observer {
/**
* 更新天气信息
*
* @param temperature 温度
* @param pressure 气压
* @param humidity 湿度
*/
void update(float temperature, float pressure, float humidity);
}
注册中心类
或者说是气象中心类,用来对各个观察者进行操作
public class WeatherData implements Subject {
private float temperature;
private float pressure;
private float humidity;
//第三方 观察者集合
private List<Observer> observers;
public WeatherData() {
observers = new ArrayList<>();
}
public float getTemperature() {
return temperature;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
//当数据有更新时,就调用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
//更新完了就推送给所有观察者
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
//注册一个观察者
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
//移除一个观察者
observers.remove(observer);
}
@Override
public void notifyObservers() {
//数据有改动就通知所有观察者
for (Observer o : observers) {
//每个观察者都执行更新方法
o.update(getTemperature(), getPressure(), getHumidity());
}
}
}
观察者类
模拟一下百度网站订阅气象网站的天气信息,观察者可以有多个,写法都差不多,就不复制其他的了
public class BaiduSite implements Observer {
private float temperature;
private float pressure;
private float humidity;
//更新天气情况
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
//显示天气信息
public void display() {
System.out.println("百度网 温度:" + temperature);
System.out.println("百度网 气压:" + pressure);
System.out.println("百度网 湿度:" + humidity);
}
}
客户获取数据
public static void main(String[] args) {
SinaSite sinaSite = new SinaSite();
BaiduSite baiduSite = new BaiduSite();
WeatherData weatherData = new WeatherData();
//注册新的观察者
weatherData.registerObserver(sinaSite);
weatherData.registerObserver(baiduSite);
//更新数据
weatherData.setData(30, 150, 40);
System.out.println("===========数据更新================");
weatherData.setData(40, 150, 20);
}
根据JDK提供类库实现观察者模式
- Observable——被观察类,底层使用Vector来存放观察者,很多方法使用了synchronized关键字,是线程安全的类
- notifyObservers——给所有观察者发消息,执行完成后恢复标记(changed=false)
- setChanged——标记对象可以更改(changed=true),可以给观察者发送消息(可以使用notifyObservers方法)
- addObserver——添加观察者
- deleteObserver——删除观察者
- countObservers——获取观察者数量
- Observer——观察者接口
- update——接收被观察者通过notifyObservers方法发送的消息
消息传递逻辑
Observable对象使用notifyObservers方法发送消息给所有Observer类
Observer对象通过update方法接收到消息
Demo逻辑:
老师订阅课程,课程有同学提问题,老师观察课程,接收到课程的问题
被观察者
课程被老师观察,收到问题立马推送给老师
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Course extends Observable {
private String courseName;
public void produceQuestion(Question question) {
//状态发生改变为true 如果是false notifyObservers方法将不生效
setChanged();
//给所有观察者发送消息 question对象作为参数传给每个观察者
//观察者会收到Observable实现类对象和参数 这里就是把自己和question传过去了
notifyObservers(question);
}
}
传递参数类
问题,由被观察者传递给观察者
@Data
@AllArgsConstructor
public class Question {
private String userName;
private String questionContent;
}
观察者类
老师接收到课程的问题
@Data
@AllArgsConstructor
public class Teacher implements Observer {
private String teacherName;
/**
* 被观察的对象和参数
*
* @param o 被观察的对象
* @param arg 传递的对象
* @return void
* @author YangYudi
* @date 2021/1/19 13:51
*/
@Override
public void update(Observable o, Object arg) {
//接收到传递的参数
Course course = (Course) o;
Question question = (Question) arg;
System.out.println(course.getCourseName() + " 课程——" + teacherName + " 老师接收到 " + question.getUserName() + " 的问题:" + question.getQuestionContent());
}
}
应用
public static void main(String[] args) {
Course course = new Course("Java设计模式");
Teacher teacher1 = new Teacher("Alpha");
Teacher teacher2 = new Teacher("Dog");
//添加观察者
course.addObserver(teacher1);
course.addObserver(teacher2);
Question question1 = new Question("张三", "没听明白");
Question question2 = new Question("李四", "代码要怎么写");
//传递两个参数 对所有观察者发送消息
course.produceQuestion(question1);
course.produceQuestion(question2);
}
SpringBoot中使用观察者模式
一个项目中一条消息可以有多种推送方式,比如站内信,短信,邮件
通过观察者模式可以在这些功能需要增加或修改时,改比较少的代码
SpringBoot中实现观察者模式主要需要写三种类型的类
- 观察者
- 事件对象
- 事件发布者
观察者用来监控事件对象,当事件发布者发布事件后,根据发布的数据各个观察者实现相应的操作
在这里的demo实现一个多种消息推送的方式
事件对象类
定义一个事件对象,需要继承spring提供的ApplicationEvent类
public class RegistryEvent extends ApplicationEvent {
/**
* 用户名
*/
private String userName;
/**
* 用户id
*/
private String userNo;
/**
* 源数据
*
* @param source 源数据
*/
public RegistryEvent(Object source) {
super(source);
}
/**
* 源数据
*/
public RegistryEvent(Object source, String userName, String userNo) {
super(source);
this.userName = userName;
this.userNo = userNo;
}
public String getUserName() {
return userName;
}
public String getUserNo() {
return userNo;
}
}
定义观察者,有两种写法,实现接口或使用注解, 要指定事件类
观察者类根据事件对象的属性完成相应的业务逻辑
观察者类
通过实现接口的观察者类写法
@Component
@Slf4j
public class EmailSend implements ApplicationListener<RegistryEvent> {
@Override
public void onApplicationEvent(RegistryEvent registryEvent) {
//打印事件对象的信息
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info("账号为{} 用户{} 注册 ,{}, 注册时间:{},备注信息:{}",
registryEvent.getUserNo(),
registryEvent.getUserName(),
"发短信通知啦",
simpleDateFormat.format(new Date()),
registryEvent.getSource());
}
}
通过注解实现的观察者类
@Component
@Slf4j
public class SmsSend {
@EventListener({RegistryEvent.class})
public void onApplicationEvent(RegistryEvent registryEvent) {
//打印事件对象的信息
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info("账号为{} 用户{} 注册 ,{}, 注册时间:{},备注信息:{}",
registryEvent.getUserNo(),
registryEvent.getUserName(),
"发邮件通知啦",
simpleDateFormat.format(new Date()),
registryEvent.getSource());
}
}
事件发布者
实现ApplicationEventPublisherAware接口,ApplicationEventPublisher对象由spring提供
使用publishEvent方法自动向观察者类推送消息
@Service
public class RegistryServiceOfEvent implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 发布事件 ( 用户注册 )
*
* @param msg
*/
public void registry(String msg, String userName, String userNo) {
// 数据校验
// 插入数据库 , 生成用户密码
// 注册成功 , 插入日志
// 发布用户注册事件
this.triggerEvent(msg, userName, userNo);
}
/**
* 发布事件 ( 用户注册 )
*
* @param msg
*/
private void triggerEvent(String msg, String userName, String userNo) {
// 发布用户注册事件
applicationEventPublisher.publishEvent(new RegistryEvent(msg, userName, userNo));
}
}
应用层的使用
@Autowired
private RegistryServiceOfEvent registryService;
@Test
void registryTestOne() {
registryService.registry("请生成一个简单点的密码", "张三", "001");
}
这样当需要增加消息推送的方式时,只需要新增一个观察者类即可,达到松耦合目的