【 设计模式 | 行为型模式 观察者模式 】
摘要:本文介绍设计模式中的观察者模式,核心是通过抽象主题、具体主题、抽象观察者、具体观察者四大角色,解决 "一对多" 依赖关系,实现主题状态变时自动通知订阅观察者、观察者同步更新的逻辑,达成两者解耦与状态变化自动化响应。
思维导图
1. 观察者模式
1.1 概述
观察者模式(又称发布 - 订阅模式)是一种解决 "一对多" 依赖关系的设计模式,它通过 抽象主题、具体主题、抽象观察者、具体观察者 这四个角色,让多个观察者对象可订阅某一主题对象;当主题对象状态改变时,会自动通知所有已订阅的观察者,使观察者无需主动查询就能同步更新自身状态,最终实现主题与观察者的解耦及状态变化的自动化响应。
1.2 结构
观察者模式包含四个核心角色,分工明确且职责递进:
抽象主题(Subject):作为被观察者的抽象定义,负责通过集合管理所有观察者,提供
addObserver()
(添加)、removeObserver()
(移除)、notifyObservers()
(通知)三个必选接口,规范观察者的管理与通知机制。具体主题(ConcreteSubject):实现抽象主题接口,存储自身具体状态;当状态发生改变时,通过调用
notifyObservers()
向所有注册的观察者发送通知。抽象观察者(Observer):作为观察者的抽象定义,仅声明一个
update()
更新接口,规定观察者接收被观察者状态变化通知后需执行更新操作。具体观察者(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 (微信用户) | 订阅被观察者,接收并处理被观察者的通知 |
抽象接口 | Subject 、Observer 接口 | 定义规范:被观察者要能 “增删订阅者、发通知”,观察者要能 “接收通知” |
二、观察者模式的核心作用:解耦 "发布者" 和 "订阅者"
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),否则新用户收不到}
}
弊端一目了然:
公众号和用户强耦合:公众号要知道所有用户的存在,还要手动管理每个用户的调用;
扩展困难:新增用户,必须修改公众号的
addUser
和sendMessage
方法,违反 "开闭原则";维护成本高:用户取消订阅时,要手动从代码中删除调用,容易漏删导致报错。
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)
定义被观察者:继承
Observable
类,在事件方法中调用setChanged()
+notifyObservers()
;定义观察者:实现
Observer
接口,重写update()
方法写响应逻辑;建立订阅关系:调用被观察者的
addObserver()
,将观察者注册到被观察者列表;触发通知:调用被观察者的事件方法,自动通知所有已注册的观察者。
核心优势:利用 Java 内置 API 简化了 “订阅列表管理”“通知遍历” 的重复代码,同时严格遵循观察者模式的 "解耦" 思想 —— 被观察者和观察者仅通过
Observable
/Observer
接口交互,互不依赖具体实现。
大功告成!