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

【 设计模式 | 行为型模式 观察者模式 】

摘要:本文介绍设计模式中的观察者模式,核心是通过抽象主题、具体主题、抽象观察者、具体观察者四大角色,解决 "一对多" 依赖关系,实现主题状态变时自动通知订阅观察者、观察者同步更新的逻辑,达成两者解耦与状态变化自动化响应。

思维导图

1. 观察者模式

1.1 概述

观察者模式(又称发布 - 订阅模式)是一种解决 "一对多" 依赖关系的设计模式,它通过 抽象主题、具体主题、抽象观察者、具体观察者 这四个角色,让多个观察者对象可订阅某一主题对象;当主题对象状态改变时,会自动通知所有已订阅的观察者,使观察者无需主动查询就能同步更新自身状态,最终实现主题与观察者的解耦及状态变化的自动化响应。

1.2 结构

观察者模式包含四个核心角色,分工明确且职责递进:

  1. 抽象主题(Subject):作为被观察者的抽象定义,负责通过集合管理所有观察者,提供addObserver()(添加)、removeObserver()(移除)、notifyObservers()(通知)三个必选接口,规范观察者的管理与通知机制。

  2. 具体主题(ConcreteSubject)实现抽象主题接口,存储自身具体状态;当状态发生改变时,通过调用notifyObservers()向所有注册的观察者发送通知

  3. 抽象观察者(Observer):作为观察者的抽象定义,仅声明一个update()更新接口,规定观察者接收被观察者状态变化通知后需执行更新操作。

  4. 具体观察者(ConcreteObserver)实现抽象观察者的update()接口,在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态。

这四个角色通过 "抽象定义规则、具体落地实现" 的方式,构建起 "主题状态变化→自动通知→观察者响应更新" 的完整逻辑闭环。

1.3 案例实现

例】微信公众号:在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

代码如下:

定义抽象观察者类,里面定义一个更新的方法

//仅声明一个update()更新接口,规定观察者接收被观察者状态变化通知后需执行更新操作
public interface Observer {void update(String message);
}

定义具体观察者类,微信用户是观察者,里面实现了更新的方法

//实现抽象观察者的update()接口
public class WeixinUser implements Observer {// 微信用户名private String name;
​public WeixinUser(String name) {this.name = name;}//在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态@Overridepublic void update(String message) {System.out.println(name + "-" + message);}
}

定义抽象主题类,提供了attach、detach、notify三个方法

//提供attach()(添加)、 detach()(移除)、notify()(通知)三个必选接口
public interface Subject {//增加订阅者public void attach(Observer observer);
​//删除订阅者public void detach(Observer observer);//通知订阅者更新消息public void notify(String message);
}

​微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法

public class SubscriptionSubject implements Subject {//储存订阅公众号的微信用户private List<Observer> weixinUserlist = new ArrayList<Observer>();
​@Overridepublic void attach(Observer observer) {weixinUserlist.add(observer);}
​@Overridepublic void detach(Observer observer) {weixinUserlist.remove(observer);}
​//当状态发生改变时,通过调用notifyObservers()向所有注册的观察者发送通知@Overridepublic void notify(String message) {for (Observer observer : weixinUserlist) {observer.update(message);}}
}

客户端程序

public class Client {public static void main(String[] args) {SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();//创建微信用户WeixinUser user1=new WeixinUser("孙悟空");WeixinUser user2=new WeixinUser("猪悟能");WeixinUser user3=new WeixinUser("沙悟净");//订阅公众号mSubscriptionSubject.attach(user1);mSubscriptionSubject.attach(user2);mSubscriptionSubject.attach(user3);//公众号更新发出消息给订阅的微信用户mSubscriptionSubject.notify("传智黑马的专栏更新了");}
}
​

1.4 代码详解1

这段代码是观察者模式的经典实现,之所以可能 "看不出来",是因为它用 "微信公众号订阅" 这个贴近生活的场景,隐藏了设计模式的 “抽象逻辑”。我们通过拆解它解决的核心问题、对比 “不用观察者模式的弊端”,就能清晰看到它的作用:

一、先明确观察者模式的核心角色(对应代码)

角色代码中的实现类职责描述
被观察者SubscriptionSubject(公众号)维护订阅者列表,当自身有更新时,通知所有订阅者
观察者WeixinUser(微信用户)订阅被观察者,接收并处理被观察者的通知
抽象接口SubjectObserver接口定义规范:被观察者要能 “增删订阅者、发通知”,观察者要能 “接收通知”

二、观察者模式的核心作用:解耦 "发布者" 和 "订阅者"

1. 没有观察者模式时:公众号如何通知用户?

如果不用观察者模式,公众号要通知用户,必须直接调用每个用户的 “接收消息” 方法。

// 无观察者模式的公众号
public class BadSubscriptionSubject {// 公众号要直接持有所有用户的引用private WeixinUser user1;private WeixinUser user2;private WeixinUser user3;
​// 新增用户时,必须修改公众号代码,加新的引用public void addUser(WeixinUser user) {if (user.getName().equals("孙悟空")) user1 = user;else if (user.getName().equals("猪悟能")) user2 = user;// ... 新增用户就要加判断,极其繁琐}
​// 发通知时,必须逐个调用用户的方法,漏一个都不行public void sendMessage(String message) {user1.update(message);user2.update(message);user3.update(message);// 新增用户后,这里必须手动加一行 user4.update(message),否则新用户收不到}
}

弊端一目了然

  • 公众号和用户强耦合:公众号要知道所有用户的存在,还要手动管理每个用户的调用;

  • 扩展困难:新增用户,必须修改公众号的addUsersendMessage方法,违反 "开闭原则"

  • 维护成本高:用户取消订阅时,要手动从代码中删除调用,容易漏删导致报错。


2. 有观察者模式时:代码如何解决这些问题?

1. 用 "列表" 代替 "单个引用":公众号用List<Observer> weixinUserlist存储用户,不管有多少用户,都统一存在列表里,不用手动加user1、user2的引用。

2. 用 "接口" 代替 "具体类":公众号依赖的是Observer接口,不是WeixinUser具体类 —— 如果未来新增 “抖音用户”(实现Observer接口),公众号无需修改,直接加入列表即可接收通知(扩展性极强)。

3. 通知逻辑 "自动化":发通知时,公众号只需遍历列表调用observer.update(message),不用关心列表里有多少用户、是谁新增 / 删除用户,只需调用attach/detach操作列表,notify方法完全不用改。

1.4 优缺点

优点:

1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。

2. 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

缺点:

1. 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时

2. 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

1.6 JDK提供

Java 中通过 java.util.Observable 类(抽象被观察者)和 java.util.Observer 接口(抽象观察者)内置实现了观察者模式,核心机制如下:

Observable 类:作为被观察者的抽象实现,内部提供三个核心方法:

  • addObserver(Observer o):将观察者添加到集合中;
  • setChange():设置内部标志为 true,标记被观察者状态已变化;
  • notifyObservers(Object arg):当 setChange() 标记为 true 时,触发集合中所有观察者的 update 方法(晚加入的观察者优先收到通知),传递变化信息。

Observer 接口:定义唯一方法 update(),当被观察者调用 notifyObservers() 时,观察者通过该方法接收通知并处理(o 为被观察者实例,arg 为传递的变化数据)。

只需基于这两个类 / 接口实现子类,即可快速构建观察者模式实例。

例】警察抓小偷:警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。

小偷是一个被观察者,所以需要继承Observable类

public class Thief extends Observable {
​private String name;
​public Thief(String name) {this.name = name;}public void setName(String name) {this.name = name;}
​public String getName() {return name;}
​public void steal() {System.out.println("小偷:我偷东西了,有没有人来抓我!!!");super.setChanged(); //changed  = truesuper.notifyObservers();}
}

警察是一个观察者,所以需要让其实现Observer接口

public class Policemen implements Observer {
​private String name;
​public Policemen(String name) {this.name = name;}public void setName(String name) {this.name = name;}
​public String getName() {return name;}
​@Overridepublic void update(Observable o, Object arg) {System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");}
}

客户端代码

public class Client {public static void main(String[] args) {//创建小偷对象Thief t = new Thief("隔壁老王");//创建警察对象Policemen p = new Policemen("小李");//让警察盯着小偷t.addObserver(p);//小偷偷东西t.steal();}
}

1.7 代码详解2

这段代码基于 Java 内置的Observable类(被观察者基类)Observer接口(观察者接口)实现了观察者模式,其核心步骤可拆解为 "定义角色 → 绑定关系 → 触发通知" 三大环节,每个环节对应明确的编码操作,适用于所有基于 Java 内置 API 的观察者模式实现:

第一步:定义观察者模式的核心角色(2 个核心角色)

观察者模式必含 "被观察者" 和 "观察者",Java 已提供现成的抽象接口 / 基类,按规则实现即可:

1. 定义 "被观察者"(需继承Observable类)

被观察者是 "事件发起者"(如案例中的小偷,偷东西是触发事件的行为),需满足:

  • 继承 Java 提供的java.util.Observable类(该类已内置 "订阅列表管理" "通知触发" 的基础逻辑,无需手动写attach/detach

  • 在 "事件发生时",先调用setChanged()标记 "状态已变化"Observable的要求:只有标记为变化,后续通知才会生效),再调用notifyObservers()触发通知(可传参数)

案例对应代码

public class Thief extends Observable { // 继承内置被观察者基类// ... 其他属性和方法// 事件触发方法(小偷偷东西)public void steal() {System.out.println("小偷:我偷东西了...");super.setChanged(); // 标记状态变化(必须有这一步,否则通知不生效)super.notifyObservers(); // 触发通知,通知所有观察者}
}

2. 定义 "观察者"(需实现Observer接口)

在 Java 的观察者模式中,观察者作为 "事件接收者"(如警察接收 "小偷作案" 通知),需满足:

1. 必须实现 Java 提供的java.util.Observer接口;

2. 必须重写接口中的update(Observable o, Object arg)方法

  • 参数o:表示触发通知的被观察者实例,可强转为具体被观察者类以获取详细信息(如从 "小偷" 实例中获取姓名);
  • 参数arg:用于接收被观察者传递的额外数据(如作案地点等补充信息);
  • 方法体:编写观察者收到通知后的具体业务逻辑(如警察根据通知执行抓捕的话术或行动)。

案例对应代码

public class Policemen implements Observer { // 实现内置观察者接口// ... 其他属性和方法// 重写通知响应方法@Overridepublic void update(Observable o, Object arg) {// 强转被观察者为具体类,获取信息Thief thief = (Thief) o;// 观察者的业务逻辑(警察响应)System.out.println("警察:" + thief.getName() + ",我盯你很久了...");}
}

第二步:建立 “被观察者 - 观察者” 的订阅关系

被观察者需要知道 "哪些观察者要接收通知",需通过Observable类内置的addObserver(Observer o)方法,将观察者 “注册” 到被观察者的 “订阅列表” 中(取消订阅用deleteObserver(Observer o))。

这一步是 "解耦的关键":被观察者只需维护 "订阅列表",无需知道观察者的具体实现,新增观察者只需加一行注册代码,无需修改被观察者逻辑。

案例对应代码

public class Client {public static void main(String[] args) {// 1. 创建被观察者实例(小偷)Thief t = new Thief("隔壁老王");// 2. 创建观察者实例(警察)Policemen p = new Policemen("小李");// 3. 建立订阅关系:警察“盯”上小偷(注册到被观察者列表)t.addObserver(p); }
}

第三步:触发事件,被观察者自动通知观察者

当被观察者发生 “目标事件”(如案例中小偷 “偷东西”),调用被观察者中定义的 “事件触发方法”(如steal()),该方法会通过setChanged()notifyObservers(),自动遍历 “订阅列表”,调用每个观察者的update()方法,完成通知。

这一步是 “自动化通知的核心”:被观察者无需手动调用每个观察者的方法,Observable基类已封装遍历逻辑,观察者只需专注于 “收到通知后做什么”。

案例对应代码

public class Client {public static void main(String[] args) {// ... 前面的实例创建、订阅关系代码// 4. 触发事件:小偷偷东西,自动通知所有观察者(警察)t.steal(); }
}

总结:观察者模式的通用实现步骤(基于 Java 内置 API)

  1. 定义被观察者:继承Observable类,在事件方法中调用setChanged()+notifyObservers()

  2. 定义观察者:实现Observer接口,重写update()方法写响应逻辑;

  3. 建立订阅关系:调用被观察者的addObserver(),将观察者注册到被观察者列表;

  4. 触发通知:调用被观察者的事件方法,自动通知所有已注册的观察者。

核心优势:利用 Java 内置 API 简化了 “订阅列表管理”“通知遍历” 的重复代码,同时严格遵循观察者模式的 "解耦" 思想 —— 被观察者和观察者仅通过Observable/Observer接口交互,互不依赖具体实现。


大功告成!

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

相关文章:

  • seo 网站案例怀化优化网站排名
  • Rust 最小可行 MQ 架构实现指南
  • 公司网站设计报价电商网站建设设计报告总结
  • 【Python】迭代器
  • 【数据迁移】:MySQL 环境下【大表定义变更】一致性保障与数据迁移优化方案
  • 织梦禁止网站右击重庆企业
  • 金融系统的“防火墙”:数字孪生如何模拟风险攻击
  • 埃拉托斯特尼筛法(Sieve of Eratosthenes)——原理、复杂度与多种 C++ 实现
  • 【大模型-金融】Trading-R1 多阶段课程学习
  • 建网站知乎怎么样上传网站资料
  • jupyter notebook 使用集锦(持续更新)
  • 部署开源PPTagent 生成工具
  • Python的大杀器:Jupyter Notebook处理.ipynb文件
  • 物流网站建设与管理规划书七牛wordpress插件
  • 【同源策略】跨域问题解决方法(多种)
  • 【数据结构】链表 --- 单链表
  • ArcGIS JSAPI 高级教程 - 自由绘制线段、多边形
  • 【2025最新】ArcGIS 点聚合功能实现全教程(进阶版)
  • Express使用教程(二)
  • 大模型部署基础设施搭建 - Docker
  • 芜湖建设机械网站企业管理系统软件下载
  • 永嘉县住房和城乡规划建设局网站自助贸易网
  • 华为云学习笔记(1):ECS 实例操作与密钥登录实践
  • 有一次django开发实录
  • RISC-V 中的 Wait For Interrupt 指令 (wfi) 详解
  • 前端核心框架vue之(指令案例篇1/5)
  • 企业静态网站源码增城建设局网站
  • 网站兼容9公司logo和商标一样吗
  • 题解:AT_abc206_e [ABC206E] Divide Both
  • 链改2.0总架构师何超秘书长重构“可信资产lPO与数链金融RWA”