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


思维导图

1. 观察者模式
1.1 概述
观察者模式(又称发布 - 订阅模式)是一种解决**"一对多"依赖关系的设计模式,它通过 抽象主题、具体主题、抽象观察者、具体观察者 这四个角色,让多个观察者对象可订阅** 某一主题对象;当主题对象状态改变时,会自动通知所有已订阅 的观察者,使观察者无需主动查询就能同步更新自身状态,最终实现主题与观察者的解耦及状态变化的自动化响应。
1.2 结构
观察者模式包含四个核心角色,分工明确且职责递进:
-
抽象主题(Subject) :作为被观察者的抽象定义,负责通过集合管理所有观察者,提供
addObserver()
(添加)、removeObserver()
(移除)、notifyObservers()
(通知)三个必选接口,规范观察者的管理与通知机制。 -
具体主题(ConcreteSubject):实现抽象主题接口 ,存储自身具体状态;当状态发生改变时,通过调用notifyObservers(
)
向所有注册的观察者发送通知。 -
抽象观察者(Observer):作为观察者的抽象定义,仅声明一个**
update()
更新接口**,规定观察者接收被观察者状态变化通知后需执行更新操作。 -
具体观察者(ConcreteObserver):实现抽象观察者的
update()
接口,在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态。
这四个角色通过 "抽象定义规则、具体落地实现" 的方式,构建起 "主题状态变化→自动通知→观察者响应更新" 的完整逻辑闭环。
1.3 案例实现
**例】微信公众号:**在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

代码如下:
定义抽象观察者类,里面定义一个更新的方法
java
//仅声明一个update()更新接口,规定观察者接收被观察者状态变化通知后需执行更新操作
public interface Observer {
void update(String message);
}
定义具体观察者类,微信用户是观察者,里面实现了更新的方法
java
//实现抽象观察者的update()接口
public class WeixinUser implements Observer {
// 微信用户名
private String name;
public WeixinUser(String name) {
this.name = name;
}
//在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
定义抽象主题类,提供了attach、detach、notify三个方法
java
//提供attach()(添加)、 detach()(移除)、notify()(通知)三个必选接口
public interface Subject {
//增加订阅者
public void attach(Observer observer);
//删除订阅者
public void detach(Observer observer);
//通知订阅者更新消息
public void notify(String message);
}
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
java
public class SubscriptionSubject implements Subject {
//储存订阅公众号的微信用户
private List<Observer> weixinUserlist = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
weixinUserlist.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserlist.remove(observer);
}
//当状态发生改变时,通过调用notifyObservers()向所有注册的观察者发送通知
@Override
public void notify(String message) {
for (Observer observer : weixinUserlist) {
observer.update(message);
}
}
}
客户端程序
java
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 (微信用户) |
订阅被观察者,接收并处理被观察者的通知 |
抽象接口 | Subject 、Observer 接口 |
定义规范:被观察者要能 "增删订阅者、发通知",观察者要能 "接收通知" |
二、观察者模式的核心作用:解耦 "发布者" 和 "订阅者"
1. 没有观察者模式时:公众号如何通知用户?
如果不用观察者模式,公众号要通知用户,必须直接调用每个用户的 "接收消息" 方法。
java
// 无观察者模式的公众号
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),否则新用户收不到
}
}
弊端一目了然:
-
公众号和用户强耦合:公众号要知道所有用户的存在,还要手动管理每个用户的调用;
-
扩展困难:新增用户,必须修改公众号的
addUser
和sendMessage
方法,违反 "开闭原则"; -
维护成本高:用户取消订阅时,要手动从代码中删除调用,容易漏删导致报错。
2. 有观察者模式时:代码如何解决这些问题?
1. 用 "列表" 代替 "单个引用" :公众号用List<Observer> weixinUserlist
存储用户,不管有多少用户,都统一存在列表里,不用手动加user1、user2
的引用。
2. 用 "接口" 代替 "具体类" :公众号依赖的是Observer
接口,不是WeixinUser
具体类 ------ 如果未来新增 "抖音用户"(实现Observer
接口),公众号无需修改,直接加入列表即可接收通知(扩展性极强)。
3. 通知逻辑 "自动化" :发通知时,公众号只需遍历列表调用observer.update(message)
,不用关心列表里有多少用户、是谁新增 / 删除用户,只需调用attach
/detach
操作列表,notify
方法完全不用改。
1.4 优缺点
优点:
-
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
-
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
缺点:
-
如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
-
如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
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类
java
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 = true
super.notifyObservers();
}
}
警察是一个观察者,所以需要让其实现Observer接口
java
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;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
客户端代码
java
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()
触发通知(可传参数)
案例对应代码:
java
public class Thief extends Observable { // 继承内置被观察者基类
// ... 其他属性和方法
// 事件触发方法(小偷偷东西)
public void steal() {
System.out.println("小偷:我偷东西了...");
super.setChanged(); // 标记状态变化(必须有这一步,否则通知不生效)
super.notifyObservers(); // 触发通知,通知所有观察者
}
}
2. 定义 "观察者"(需实现Observer
接口)
在 Java 的观察者模式中,观察者作为 "事件接收者"(如警察接收 "小偷作案" 通知),需满足:
-
必须实现 Java 提供的
java.util.Observer
接口; -
必须重写接口中的
update(Observable o, Object arg)
方法:
- 参数
o
:表示触发通知的被观察者实例,可强转为具体被观察者类以获取详细信息(如从 "小偷" 实例中获取姓名); - 参数
arg
:用于接收被观察者传递的额外数据(如作案地点等补充信息); - 方法体:编写观察者收到通知后的具体业务逻辑(如警察根据通知执行抓捕的话术或行动)。
案例对应代码:
java
public class Policemen implements Observer { // 实现内置观察者接口
// ... 其他属性和方法
// 重写通知响应方法
@Override
public void update(Observable o, Object arg) {
// 强转被观察者为具体类,获取信息
Thief thief = (Thief) o;
// 观察者的业务逻辑(警察响应)
System.out.println("警察:" + thief.getName() + ",我盯你很久了...");
}
}
第二步:建立 "被观察者 - 观察者" 的订阅关系
被观察者需要知道 "哪些观察者要接收通知",需通过Observable
类内置的addObserver(Observer o)
方法,将观察者 "注册" 到被观察者的 "订阅列表" 中(取消订阅用deleteObserver(Observer o)
)。
这一步是 "解耦的关键":被观察者只需维护 "订阅列表",无需知道观察者的具体实现,新增观察者只需加一行注册代码,无需修改被观察者逻辑。
案例对应代码:
java
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
基类已封装遍历逻辑,观察者只需专注于 "收到通知后做什么"。
案例对应代码:
java
public class Client {
public static void main(String[] args) {
// ... 前面的实例创建、订阅关系代码
// 4. 触发事件:小偷偷东西,自动通知所有观察者(警察)
t.steal();
}
}
总结:观察者模式的通用实现步骤(基于 Java 内置 API)
定义被观察者 :继承
Observable
类,在事件方法中调用setChanged()
+notifyObservers()
;定义观察者 :实现
Observer
接口,重写update()
方法写响应逻辑;建立订阅关系 :调用被观察者的
addObserver()
,将观察者注册到被观察者列表;触发通知:调用被观察者的事件方法,自动通知所有已注册的观察者。
核心优势: 利用 Java 内置 API 简化了 "订阅列表管理""通知遍历" 的重复代码,同时严格遵循观察者模式的 "解耦" 思想 ------ 被观察者和观察者仅通过
Observable
/Observer
接口交互,互不依赖具体实现。
大功告成!