假设有一个服务可供订阅:天气预报服务
每个系统都可以订阅这个服务,天气预报过一段时间就更新信息,一旦更新也要把更新后的信息推送给各个订阅者
观察者模式的作用有点像消息队列
这大概就是观察者模式的主要作用,信息一更新马上就给所有观察者推送
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;}//当数据有更新时,就调用 setDatapublic void setData(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;//更新完了就推送给所有观察者notifyObservers();}@Overridepublic void registerObserver(Observer observer) {//注册一个观察者observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {//移除一个观察者observers.remove(observer);}@Overridepublic 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;//更新天气情况@Overridepublic 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@AllArgsConstructorpublic class Course extends Observable {private String courseName;public void produceQuestion(Question question) {//状态发生改变为true 如果是false notifyObservers方法将不生效setChanged();//给所有观察者发送消息 question对象作为参数传给每个观察者//观察者会收到Observable实现类对象和参数 这里就是把自己和question传过去了notifyObservers(question);}}
传递参数类
问题,由被观察者传递给观察者
@Data@AllArgsConstructorpublic class Question {private String userName;private String questionContent;}
观察者类
老师接收到课程的问题
@Data@AllArgsConstructorpublic class Teacher implements Observer {private String teacherName;/*** 被观察的对象和参数** @param o 被观察的对象* @param arg 传递的对象* @return void* @author YangYudi* @date 2021/1/19 13:51*/@Overridepublic 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@Slf4jpublic class EmailSend implements ApplicationListener<RegistryEvent> {@Overridepublic 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@Slf4jpublic 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方法自动向观察者类推送消息
@Servicepublic class RegistryServiceOfEvent implements ApplicationEventPublisherAware {private ApplicationEventPublisher applicationEventPublisher;@Overridepublic 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));}}
应用层的使用
@Autowiredprivate RegistryServiceOfEvent registryService;@Testvoid registryTestOne() {registryService.registry("请生成一个简单点的密码", "张三", "001");}
这样当需要增加消息推送的方式时,只需要新增一个观察者类即可,达到松耦合目的
