假设有一个服务可供订阅:天气预报服务

每个系统都可以订阅这个服务,天气预报过一段时间就更新信息,一旦更新也要把更新后的信息推送给各个订阅者

观察者模式的作用有点像消息队列

这大概就是观察者模式的主要作用,信息一更新马上就给所有观察者推送

Java原生手写写法

先创建一个相当于注册中心的接口,对观察者进行操作

  1. public interface Subject {
  2. /**
  3. * 添加一个观察者
  4. * @param observer
  5. */
  6. void registerObserver(Observer observer);
  7. /**
  8. * 移除一个观察者
  9. * @param observer
  10. */
  11. void removeObserver(Observer observer);
  12. /**
  13. * 对所有观察者进行通知
  14. */
  15. void notifyObservers();
  16. }

定义一个观察者接口,因为举例和天气有关,所以提供一个更新方法,每个观察者订阅后都可以更新自己系统显示的天气信息

  1. public interface Observer {
  2. /**
  3. * 更新天气信息
  4. *
  5. * @param temperature 温度
  6. * @param pressure 气压
  7. * @param humidity 湿度
  8. */
  9. void update(float temperature, float pressure, float humidity);
  10. }

注册中心类

或者说是气象中心类,用来对各个观察者进行操作

  1. public class WeatherData implements Subject {
  2. private float temperature;
  3. private float pressure;
  4. private float humidity;
  5. //第三方 观察者集合
  6. private List<Observer> observers;
  7. public WeatherData() {
  8. observers = new ArrayList<>();
  9. }
  10. public float getTemperature() {
  11. return temperature;
  12. }
  13. public float getPressure() {
  14. return pressure;
  15. }
  16. public float getHumidity() {
  17. return humidity;
  18. }
  19. //当数据有更新时,就调用 setData
  20. public void setData(float temperature, float pressure, float humidity) {
  21. this.temperature = temperature;
  22. this.pressure = pressure;
  23. this.humidity = humidity;
  24. //更新完了就推送给所有观察者
  25. notifyObservers();
  26. }
  27. @Override
  28. public void registerObserver(Observer observer) {
  29. //注册一个观察者
  30. observers.add(observer);
  31. }
  32. @Override
  33. public void removeObserver(Observer observer) {
  34. //移除一个观察者
  35. observers.remove(observer);
  36. }
  37. @Override
  38. public void notifyObservers() {
  39. //数据有改动就通知所有观察者
  40. for (Observer o : observers) {
  41. //每个观察者都执行更新方法
  42. o.update(getTemperature(), getPressure(), getHumidity());
  43. }
  44. }
  45. }

观察者类

模拟一下百度网站订阅气象网站的天气信息,观察者可以有多个,写法都差不多,就不复制其他的了

  1. public class BaiduSite implements Observer {
  2. private float temperature;
  3. private float pressure;
  4. private float humidity;
  5. //更新天气情况
  6. @Override
  7. public void update(float temperature, float pressure, float humidity) {
  8. this.temperature = temperature;
  9. this.pressure = pressure;
  10. this.humidity = humidity;
  11. display();
  12. }
  13. //显示天气信息
  14. public void display() {
  15. System.out.println("百度网 温度:" + temperature);
  16. System.out.println("百度网 气压:" + pressure);
  17. System.out.println("百度网 湿度:" + humidity);
  18. }
  19. }

客户获取数据

  1. public static void main(String[] args) {
  2. SinaSite sinaSite = new SinaSite();
  3. BaiduSite baiduSite = new BaiduSite();
  4. WeatherData weatherData = new WeatherData();
  5. //注册新的观察者
  6. weatherData.registerObserver(sinaSite);
  7. weatherData.registerObserver(baiduSite);
  8. //更新数据
  9. weatherData.setData(30, 150, 40);
  10. System.out.println("===========数据更新================");
  11. weatherData.setData(40, 150, 20);
  12. }

这样就实现了一个简单的观察者模式
image.png

根据JDK提供类库实现观察者模式

  • Observable——被观察类,底层使用Vector来存放观察者,很多方法使用了synchronized关键字,是线程安全的类
    • notifyObservers——给所有观察者发消息,执行完成后恢复标记(changed=false)
    • setChanged——标记对象可以更改(changed=true),可以给观察者发送消息(可以使用notifyObservers方法)
    • addObserver——添加观察者
    • deleteObserver——删除观察者
    • countObservers——获取观察者数量
  • Observer——观察者接口
    • update——接收被观察者通过notifyObservers方法发送的消息

消息传递逻辑

Observable对象使用notifyObservers方法发送消息给所有Observer
Observer对象通过update方法接收到消息

Demo逻辑:
老师订阅课程,课程有同学提问题,老师观察课程,接收到课程的问题

被观察者

课程被老师观察,收到问题立马推送给老师

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class Course extends Observable {
  5. private String courseName;
  6. public void produceQuestion(Question question) {
  7. //状态发生改变为true 如果是false notifyObservers方法将不生效
  8. setChanged();
  9. //给所有观察者发送消息 question对象作为参数传给每个观察者
  10. //观察者会收到Observable实现类对象和参数 这里就是把自己和question传过去了
  11. notifyObservers(question);
  12. }
  13. }

传递参数类

问题,由被观察者传递给观察者

  1. @Data
  2. @AllArgsConstructor
  3. public class Question {
  4. private String userName;
  5. private String questionContent;
  6. }

观察者类

老师接收到课程的问题

  1. @Data
  2. @AllArgsConstructor
  3. public class Teacher implements Observer {
  4. private String teacherName;
  5. /**
  6. * 被观察的对象和参数
  7. *
  8. * @param o 被观察的对象
  9. * @param arg 传递的对象
  10. * @return void
  11. * @author YangYudi
  12. * @date 2021/1/19 13:51
  13. */
  14. @Override
  15. public void update(Observable o, Object arg) {
  16. //接收到传递的参数
  17. Course course = (Course) o;
  18. Question question = (Question) arg;
  19. System.out.println(course.getCourseName() + " 课程——" + teacherName + " 老师接收到 " + question.getUserName() + " 的问题:" + question.getQuestionContent());
  20. }
  21. }

应用

  1. public static void main(String[] args) {
  2. Course course = new Course("Java设计模式");
  3. Teacher teacher1 = new Teacher("Alpha");
  4. Teacher teacher2 = new Teacher("Dog");
  5. //添加观察者
  6. course.addObserver(teacher1);
  7. course.addObserver(teacher2);
  8. Question question1 = new Question("张三", "没听明白");
  9. Question question2 = new Question("李四", "代码要怎么写");
  10. //传递两个参数 对所有观察者发送消息
  11. course.produceQuestion(question1);
  12. course.produceQuestion(question2);
  13. }

一个课程的两个老师,都收到了这个课程里的学生提出的问题
image.png

SpringBoot中使用观察者模式

一个项目中一条消息可以有多种推送方式,比如站内信,短信,邮件
通过观察者模式可以在这些功能需要增加或修改时,改比较少的代码

SpringBoot中实现观察者模式主要需要写三种类型的类

  • 观察者
  • 事件对象
  • 事件发布者

观察者用来监控事件对象,当事件发布者发布事件后,根据发布的数据各个观察者实现相应的操作

在这里的demo实现一个多种消息推送的方式

事件对象类

定义一个事件对象,需要继承spring提供的ApplicationEvent类

  1. public class RegistryEvent extends ApplicationEvent {
  2. /**
  3. * 用户名
  4. */
  5. private String userName;
  6. /**
  7. * 用户id
  8. */
  9. private String userNo;
  10. /**
  11. * 源数据
  12. *
  13. * @param source 源数据
  14. */
  15. public RegistryEvent(Object source) {
  16. super(source);
  17. }
  18. /**
  19. * 源数据
  20. */
  21. public RegistryEvent(Object source, String userName, String userNo) {
  22. super(source);
  23. this.userName = userName;
  24. this.userNo = userNo;
  25. }
  26. public String getUserName() {
  27. return userName;
  28. }
  29. public String getUserNo() {
  30. return userNo;
  31. }
  32. }

定义观察者,有两种写法,实现接口或使用注解, 要指定事件类
观察者类根据事件对象的属性完成相应的业务逻辑

观察者类

通过实现接口的观察者类写法

  1. @Component
  2. @Slf4j
  3. public class EmailSend implements ApplicationListener<RegistryEvent> {
  4. @Override
  5. public void onApplicationEvent(RegistryEvent registryEvent) {
  6. //打印事件对象的信息
  7. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  8. log.info("账号为{} 用户{} 注册 ,{}, 注册时间:{},备注信息:{}",
  9. registryEvent.getUserNo(),
  10. registryEvent.getUserName(),
  11. "发短信通知啦",
  12. simpleDateFormat.format(new Date()),
  13. registryEvent.getSource());
  14. }
  15. }

通过注解实现的观察者类

  1. @Component
  2. @Slf4j
  3. public class SmsSend {
  4. @EventListener({RegistryEvent.class})
  5. public void onApplicationEvent(RegistryEvent registryEvent) {
  6. //打印事件对象的信息
  7. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  8. log.info("账号为{} 用户{} 注册 ,{}, 注册时间:{},备注信息:{}",
  9. registryEvent.getUserNo(),
  10. registryEvent.getUserName(),
  11. "发邮件通知啦",
  12. simpleDateFormat.format(new Date()),
  13. registryEvent.getSource());
  14. }
  15. }

事件发布者

实现ApplicationEventPublisherAware接口,ApplicationEventPublisher对象由spring提供
使用publishEvent方法自动向观察者类推送消息

  1. @Service
  2. public class RegistryServiceOfEvent implements ApplicationEventPublisherAware {
  3. private ApplicationEventPublisher applicationEventPublisher;
  4. @Override
  5. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  6. this.applicationEventPublisher = applicationEventPublisher;
  7. }
  8. /**
  9. * 发布事件 ( 用户注册 )
  10. *
  11. * @param msg
  12. */
  13. public void registry(String msg, String userName, String userNo) {
  14. // 数据校验
  15. // 插入数据库 , 生成用户密码
  16. // 注册成功 , 插入日志
  17. // 发布用户注册事件
  18. this.triggerEvent(msg, userName, userNo);
  19. }
  20. /**
  21. * 发布事件 ( 用户注册 )
  22. *
  23. * @param msg
  24. */
  25. private void triggerEvent(String msg, String userName, String userNo) {
  26. // 发布用户注册事件
  27. applicationEventPublisher.publishEvent(new RegistryEvent(msg, userName, userNo));
  28. }
  29. }

应用层的使用

  1. @Autowired
  2. private RegistryServiceOfEvent registryService;
  3. @Test
  4. void registryTestOne() {
  5. registryService.registry("请生成一个简单点的密码", "张三", "001");
  6. }

这样当需要增加消息推送的方式时,只需要新增一个观察者类即可,达到松耦合目的