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

深入解析:设计模式第六章(观察者模式)

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

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

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

前言

关键角色

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

工作流程

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

典型场景

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

优点

例如,在天气系统中:

实战第一版本

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

用户信息

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 {
@Override
public 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 {
      @EventListener
      public void registerEvent(RegisterEvent event) {
      String userName = event.getUserName();
      System.out.println("发放新手礼品给: " + userName);
      }
      }
  • 邮件通知

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

在这里插入图片描述

源码分析部分

在这里插入图片描述

在这里插入图片描述

http://www.hskmm.com/?act=detail&tid=35743

相关文章:

  • CallbackData错误原因分析
  • 2025微弧氧化加工厂家推荐:常州华源专业表面处理技术供应商
  • hash判断两个集合是否完全相同
  • 2025滑触线实力厂家推荐,无锡宸澳电气多型号防爆安全定制!
  • 2025年GEO优化公司推荐:五大实力企业口碑榜,引领AI搜索营销新生态
  • 2025年10月全屋智能家居品牌推荐:盈趣领衔对比评测榜
  • 2025码垛机厂家推荐济南金瑞祥,全自动龙门桁架定制实力企业
  • 2025防腐工程厂家推荐:无锡华金喷涂技术领先,定制防腐解决方案
  • [LangChian] 05.结构化提示词
  • C#获取文件md5码
  • 2025年10月防腐木凉亭厂家对比评测榜:江西纳美领衔五强深度解析
  • 2025通风天窗实力厂家推荐,正鑫专业制造与定制服务保障
  • 2025年10月治鼻炎产品推荐:权威对比评测榜助您精准选购
  • git提PR时很多别人的commit,清理多余的commit
  • Visual Studio 使用小知识记录
  • 2025数控锯床厂家推荐无锡正川,专业立式锯床制造企业
  • DeepSeek-OCR:让 AI “一眼看懂” 的黑科技
  • 生成一张图,苹果logo是透明冰块,安卓小机器人撒尿到苹果logo,冲出一个豁口
  • 业务记录:登录
  • kafka2.8出现NotLeaderOrFollowerException
  • IEC 61850 ICD文件解析
  • 2025无锡新梅赛智能设备厂家推荐:全自动视觉定位点胶机专业制造商
  • 2025安全光栅厂家推荐安一光电,超薄无盲区设计守护工业安全
  • 2025石头纸设备厂家权威推荐:鼎浩包装科技环保吹塑机制造专家
  • Java面试题总结
  • 读书笔记:Oracle分区技术详解
  • 2025精密光电厂家推荐:柯依努UV固化设备专业定制,品质保障!
  • 徐老师2025新版Nodejs课程含项目实战
  • 详细介绍:isis整体知识梳理
  • Moe-ctf Misc部分题解