当前位置: 首页 > news >正文

设计模式第六章(观察者模式)

设计模式第六章(观察者模式)

​ 观察者模式是一种行为设计模式,它定义了对象之间的一对对多依赖关系:当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。

这种模式的核心思想是解耦被观察者和观察者,让它们可以独立变化,同时保持联动。

前言

关键角色

  1. 被观察者(Observable/Subject)
    • 维护一个观察者列表,提供添加、移除观察者的方法
    • 当自身状态变化时,主动通知所有注册的观察者
  2. 观察者(Observer)
    • 定义一个更新接口,当收到被观察者的通知时,执行相应的处理逻辑
    • 可以有多个不同的观察者实现,各自处理方式不同

工作流程

  1. 观察者通过被观察者提供的方法(如 addObserver())注册自己
  2. 被观察者状态发生改变时,调用自身的通知方法(如 notifyObservers()
  3. 被观察者遍历所有注册的观察者,调用它们的更新方法(如 update()
  4. 观察者收到通知后,根据被观察者的状态变化执行具体操作

典型场景

  • 消息订阅:公众号(被观察者)更新文章后,所有订阅者(观察者)收到推送
  • 数据监控:传感器(被观察者)检测到温度变化,仪表盘、报警器(观察者)分别响应
  • GUI 事件处理:按钮(被观察者)被点击时,关联的回调函数(观察者)执行

优点

  • 降低耦合:被观察者无需知道观察者的具体实现,只需调用统一的更新接口
  • 扩展性好:新增观察者时,无需修改被观察者代码,符合 “开闭原则”
  • 联动灵活:可以动态添加 / 移除观察者,实时调整响应关系

例如,在天气系统中:

  • 气象站(被观察者)收集到温度变化
  • 手机 APP、显示屏、报警器(多个观察者)会同时收到通知并更新显示或触发警报
  • 若后续需要添加新的观察者(如恒温控制器),只需让它实现观察者接口并注册即可,无需修改气象站代码。

实战第一版本

故事背景:一个天气站如果更新了天气,需要将天气通知订阅了天气更新的某些人和事。我们来用一个初级版本直接堆功能来实现。

用户信息

public class User {private final String name;private final Consumer<String> consumer;public User(String name, Consumer<String> consumer) {this.name = name;this.consumer = consumer;}public void notify(String inf) {consumer.accept(inf);}
}

天气站

public class WeatherStation {private final List<User> users = new ArrayList<User>();public void addUser(User user) {users.add(user);}public String getInfo() {if (new Random().nextBoolean()) {return "晴天";}return "雨天";}public void start() {while (true) {String info = getInfo();for (User user : users) {user.notify(info);}try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

测试用例

public class Main {public static void main(String[] args) {//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text// to see how IntelliJ IDEA suggests fixing it.System.out.println("Hello and welcome!");User tom = new User("tom", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,tom快出来玩啊~~~");} else {System.out.println("下雨天,tom 你需要在家待着了~~~");}});User jerry = new User("jerry", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,jerry快出来游泳了~~");}});//订阅天气信息WeatherStation station = new WeatherStation();station.addUser(tom);station.addUser(jerry);station.start();// 问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢,// 通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢// 接下来,我们就需要一个tvstation来进行通知}
}

在这里插入图片描述

问题点

  • 问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢

  • 通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢

  • 接下来,我们就需要一个tvstation来进行通知

实战第二版本

​ 我们增加了一个电视台的角色,所有的订阅都去电视台,天气信息的更新我只负责通知到电视台,由电视台再通知被订阅的人

用户信息

public class User {private final String name;private final Consumer<String> consumer;public User(String name, Consumer<String> consumer) {this.name = name;this.consumer = consumer;}public void notify(String inf) {consumer.accept(inf);}
}

电视台

  • 提供一个订阅的入口
  • 提供一个发布的入口
public class TvStation {private final List<User> userList = new ArrayList<>();// 订阅的入口public void addUser(User user) {userList.add(user);}public void publish(String inf) {for (User user : userList) {user.notify(inf);}}}

天气站

public class WeatherStation {//电视台private final TvStation tvStation;public WeatherStation(TvStation tvStation) {this.tvStation = tvStation;}public String getInfo() {if (new Random().nextBoolean()) {return "晴天";}return "雨天";}public void start() {while (true) {String info = getInfo();//我只需要把我得消息给到电视台,由电视台进行这个通知的动作tvStation.publish(info);try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

测试用例

public class Main {public static void main(String[] args) {//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text// to see how IntelliJ IDEA suggests fixing it.System.out.println("Hello and welcome!");User tom = new User("tom", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,tom快出来玩啊~~~");} else {System.out.println("下雨天,tom 你需要在家待着了~~~");}});User jerry = new User("jerry", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,jerry快出来游泳了~~");}});TvStation tvStation = new TvStation();//两个用户订阅了电视台的消息tvStation.addUser(tom);tvStation.addUser(jerry);// 获取天气信息WeatherStation weatherStation = new WeatherStation(tvStation);weatherStation.start();// 天气只负责获取信息,通知由电视台进行// 优化点,如果用户需要订阅 增加了一个新闻类的信息该怎么办呢// 下一节,我们将抽象发布布订阅模式}
}

在这里插入图片描述

问题点

  • 如果用户需要订阅 一个新闻类的信息该怎么办呢,我们是不是需要在里面再继续加一个新闻的类的,下一章节使用订阅发布模式来解耦

实战第三版本

  • 事件,所有的需要通知的事件都需要实现该接口。
  • 事件监听,所有希望得到通知的都需要实现该接口

事件总线接口

public interface Event {/***  时间戳* @return*/long timestamp();/***  数据个格式* @return*/Object source();
}

事件总线抽象基类

public abstract class BaseEvent implements Event {@Overridepublic long timestamp() {return System.currentTimeMillis();}
}

天气更新的事件

如果天气更新了,那么我事先事件这个接口,信息都包装为一个 event 事件信息

public class WeatherUpdEvent extends BaseEvent{//天气信息private final String info;public WeatherUpdEvent(String info) {this.info = info;}@Overridepublic Object source() {return info;}
}

事件监听

public interface ListenerEvent {/***  监听事件* @param event*/void onEvent(Event event);
}

订阅感兴趣的事件

实际为最终消费端。拿到事件,然后做什么

public class User implements ListenerEvent {private final String name;private final Consumer<String> consumer;public User(String name, Consumer<String> consumer) {this.name = name;this.consumer = consumer;}private void notify(String inf) {consumer.accept(inf);}@Overridepublic void onEvent(Event event) {if (event instanceof WeatherUpdEvent) {notify(event.source().toString());}}}

事件总线

我们监听订阅了event时间下所有的订阅者。

public class TvStation {private final Map<Class<? extends Event>,List<ListenerEvent>> listenerEventMap = new HashMap<>();// 订阅的入口public void subscribe(ListenerEvent event,Class<? extends Event> eventType) {listenerEventMap.computeIfAbsent(eventType,k -> new ArrayList<>()).add(event);}public void publish(Event event) {Class<? extends Event> evnetClass = event.getClass();List<ListenerEvent> listenerEvents = listenerEventMap.get(evnetClass);if (listenerEvents != null) {for (ListenerEvent listener : listenerEvents) {listener.onEvent(event);}}}}

天气站

我们需要将我们的天气信息包装为一个event 的事件,这个时候就是天气更新的事件,我们进行包装一次。

public class WeatherStation {//电视台  我们成为消息总线private final TvStation tvStation;public WeatherStation(TvStation tvStation) {this.tvStation = tvStation;}public String getInfo() {if (new Random().nextBoolean()) {return "晴天";}return "雨天";}public void start() {while (true) {String info = getInfo();WeatherUpdEvent weatherUpdEvent = new WeatherUpdEvent(info);tvStation.publish(weatherUpdEvent);try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

测试用例

public class Main {public static void main(String[] args) {User tom = new User("tom", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,tom快出来玩啊~~~");} else {System.out.println("下雨天,tom 你需要在家待着了~~~");}});User jerry = new User("jerry", inf -> {if (inf.equals("晴天")) {System.out.println("晴天了,jerry快出来游泳了~~");}});TvStation tvStation = new TvStation();tvStation.subscribe(tom, WeatherUpdEvent.class);tvStation.subscribe(jerry, WeatherUpdEvent.class);WeatherStation station = new WeatherStation(tvStation);station.start();}
}

在这里插入图片描述

spring使用观察者模式

@Autowired
private ApplicationContext context;// 事件发布器
@Autowired
private ApplicationEventPublisher publisher;// 事件广播
@Autowired
private ApplicationEventMulticaster multicaster;

ApplicationContext

我们看到 ApplicationContext 继承ApplicationEventPublisher,那么这两个是不是就是一个对象呢。

在这里插入图片描述

实战部分

我们定义了一个controller,当用户注册的时候,我们需要像用户发送邮件,送新手礼物。

   @GetMapping("regiest")public String regiest(@RequestParam("userName") String userName) {publisher.publishEvent(new RegisterEvent(userName));//context.publishEvent(new RegisterEvent(userName));//multicaster.multicastEvent(new RegisterEvent(userName));return "success";}
定义用户注册的事件
public class RegisterEvent extends ApplicationEvent {public RegisterEvent(Object user) {super(user);}public String getUserName() {return getSource().toString();}
}
监听用户注册成功事件
  • 发放礼包

    • @Service
      public class GiftService {@EventListenerpublic void registerEvent(RegisterEvent event) {String userName = event.getUserName();System.out.println("发放新手礼品给: " + userName);}
      }
      
  • 邮件通知

    • @Service
      public class EmailService {@EventListenerpublic void registerEvent(RegisterEvent event) {String userName = event.getUserName();System.out.println("给用户: " + userName + "发送邮件");}
      }
      
启动类测试

在这里插入图片描述

源码分析部分

  • org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
    • org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
      • org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
        • org.springframework.context.event.ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

在这里插入图片描述

在这里插入图片描述

http://www.dtcms.com/a/435289.html

相关文章:

  • C4D R20新增功能平滑滤镜和调整外形滤镜深度解析
  • 数据安全风险评估
  • 动漫共和国 | window版本
  • 校园网站如何建立在浙学网页设计与制作答案
  • 企业创建网站的途径都有啥深圳市浩天建设网站
  • 做外贸国外网站苏宁电器网站建设特点分析
  • AI自动化测试:接口测试全流程自动化的实现方法——技术深度与行业实践剖析
  • LeeCode 328. 奇偶链表
  • 【Linux CentOS 7 版本更换yum源】
  • ⚡ WSL2 搭建 s5p6818 Linux 嵌入式开发平台 (part 3):Wifi驱动移植、ssh移植、e2fsprogs移植
  • 建网站怎么弄在线制作app平台
  • 进程的概念(下)
  • 网站开发安装win10家庭版广告设计公司组织结构图
  • 自媒体时代做网站有前途吗网站备案审批号
  • 为何“过度工程”会发生
  • Linux多线程服务端编程:使用muduo C++网络库学习之环境配置
  • 新质生产力
  • 嵌入式八股文篇——P1 关键字篇
  • 河源正规网站建设价格广东省深圳市公司
  • Core Speech Kit简介
  • 【OTA专题】3.实现简单的boot和APP程序逻辑
  • 营销单页模板网站怎么买网站域名
  • 织梦做商城网站wordpress 自建邮件
  • 小江网站建设必须重视的问题之一
  • Bella Beauty WordPress Theme — Aesthetic Medical Clinic
  • Java_钻石操作符详解
  • 网站做qq微信微博登录爱做的小说网站
  • 大文件推送到git仓库
  • 对招聘网站页面设计做建议wordpress主题cute
  • Spring cloud快速入门