一、观察者模式概述
观察者模式的定义与核心思想
定义
观察者模式(Observer Pattern)是一种行为型设计模式,定义了对象间的一种一对多依赖关系。当一个对象(称为Subject,主题/被观察者)的状态发生改变时,所有依赖它的对象(称为Observers,观察者)会自动收到通知并更新。
核心思想
-
解耦:
- 被观察者无需关心观察者的具体实现,仅通过抽象接口(如
Observer
接口)通知变化。 - 观察者可以动态注册或注销,系统灵活性高。
- 被观察者无需关心观察者的具体实现,仅通过抽象接口(如
-
事件驱动:
- 状态变化触发通知机制,观察者被动响应,而非主动轮询状态。
-
开放封闭原则:
- 新增观察者无需修改被观察者代码,符合“对扩展开放,对修改封闭”原则。
关键角色
-
Subject(被观察者)
- 维护观察者列表(如
List<Observer>
)。 - 提供注册(
attach
)、注销(detach
)和通知(notify
)方法。
- 维护观察者列表(如
-
Observer(观察者)
- 定义更新接口(如
update()
),供被观察者调用以传递状态变化。
- 定义更新接口(如
类比生活场景
- 报纸订阅:
- 出版社(Subject)维护订阅者(Observers)列表,新刊发布时自动邮寄给所有订阅者。
- 订阅者无需主动询问出版社是否有新刊。
设计模式分类(行为型模式)
概念定义
行为型模式(Behavioral Patterns)是设计模式的一种分类,主要关注对象之间的职责分配和算法封装。这类模式描述的是对象或类如何交互以及如何分配职责,使得系统更灵活、可扩展。
行为型模式的核心特点
- 对象间的交互:关注对象之间的通信方式(如观察者模式中的发布-订阅机制)。
- 职责分离:将特定行为封装到独立的类中(如策略模式)。
- 算法封装:将复杂逻辑解耦(如模板方法模式)。
常见的行为型模式
以下是 Java 中常用的行为型模式及其简要说明:
1. 观察者模式(Observer)
- 作用:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象自动收到通知。
- 示例场景:事件监听、消息推送系统。
2. 策略模式(Strategy)
- 作用:定义一系列算法,将每个算法封装起来并使它们可以互换。
- 示例场景:支付方式选择(支付宝、微信支付等)。
3. 模板方法模式(Template Method)
- 作用:定义一个算法的骨架,将某些步骤延迟到子类中实现。
- 示例场景:JdbcTemplate 的数据库操作流程。
4. 责任链模式(Chain of Responsibility)
- 作用:将请求的发送者和接收者解耦,使多个对象都有机会处理请求。
- 示例场景:过滤器链(如 Servlet Filter)。
5. 命令模式(Command)
- 作用:将请求封装为对象,支持请求的排队、日志记录或撤销操作。
- 示例场景:GUI 的撤销/重做功能。
6. 状态模式(State)
- 作用:允许对象在其内部状态改变时改变行为。
- 示例场景:订单状态流转(待支付、已发货等)。
7. 访问者模式(Visitor)
- 作用:将算法与对象结构分离,在不修改对象结构的前提下新增操作。
- 示例场景:编译器对抽象语法树的处理。
8. 中介者模式(Mediator)
- 作用:用一个中介对象封装一系列对象的交互,降低耦合。
- 示例场景:聊天室中的消息转发。
9. 迭代器模式(Iterator)
- 作用:提供一种方法顺序访问聚合对象的元素,而不暴露其内部表示。
- 示例场景:Java 中的
Iterator
接口。
10. 备忘录模式(Memento)
- 作用:捕获并外部化对象的内部状态,以便后续恢复。
- 示例场景:游戏存档功能。
11. 解释器模式(Interpreter)
- 作用:定义语言的文法,并解释执行语句。
- 示例场景:正则表达式解析。
行为型模式的共性
- 解耦:减少对象间的直接依赖(如通过中介者或观察者)。
- 扩展性:通过组合或继承灵活扩展行为(如策略模式)。
- 复用性:将通用逻辑抽象到父类或独立类中(如模板方法模式)。
如何选择行为型模式?
问题场景 | 推荐模式 |
---|---|
需要动态切换算法或策略 | 策略模式 |
对象状态影响行为 | 状态模式 |
一对多的依赖通知 | 观察者模式 |
需要分离操作逻辑与数据结构 | 访问者模式 |
处理复杂请求链 | 责任链模式 |
示例代码(策略模式)
// 策略接口
interface PaymentStrategy {
void pay(int amount);
}
// 具体策略
class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("支付宝支付:" + amount);
}
}
class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("微信支付:" + amount);
}
}
// 上下文类
class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(int amount) {
strategy.pay(amount);
}
}
// 使用示例
public class Client {
public static void main(String[] args) {
PaymentContext context = new PaymentContext();
context.setStrategy(new AlipayStrategy());
context.executePayment(100); // 输出:支付宝支付:100
}
}
解决的问题场景
观察者模式(Observer Pattern)是一种行为设计模式,主要用于解决对象间的一对多依赖关系。当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会自动收到通知并更新。
核心问题
- 对象间的动态联动:当一个对象的状态变化需要触发其他对象的联动行为时,如何避免紧耦合?
- 实时通知机制:如何让多个对象在目标对象状态变化时,第一时间获取更新?
- 可扩展性:如何在不修改被观察者代码的情况下,动态增加或删除观察者?
典型应用场景
-
事件驱动系统
- 例如 GUI 中的按钮点击事件(按钮是被观察者,事件监听器是观察者)。
- 用户操作(如鼠标移动、键盘输入)触发多个组件的响应。
-
数据监控与通知
- 股票价格变动时,通知所有订阅该股票的投资者。
- 传感器数据更新后,触发多个数据分析模块。
-
发布-订阅模型
- 消息队列(如 Kafka、RabbitMQ)中,生产者发布消息,多个消费者订阅并处理。
- 社交媒体中,用户发布动态后,粉丝自动收到推送。
-
状态同步
- 游戏开发中,角色血量变化时,UI 血条、音效、成就系统等需要同步更新。
- 分布式系统中,配置中心的变更需要同步到所有服务节点。
代码示例场景
假设有一个气象站(被观察者),当温度变化时,需要通知多个显示设备(观察者)更新数据:
// 被观察者接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(float temperature);
}
// 具体被观察者:气象站
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers(); // 温度变化时通知所有观察者
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature);
}
}
}
// 具体观察者:手机显示
class PhoneDisplay implements Observer {
@Override
public void update(float temperature) {
System.out.println("手机显示温度更新: " + temperature);
}
}
注意事项
- 避免循环依赖:观察者中不要反向调用被观察者的方法,否则可能导致无限递归。
- 线程安全:多线程环境下,需保证观察者注册/通知操作的原子性(如使用
CopyOnWriteArrayList
)。 - 性能问题:观察者数量过多时,同步通知可能成为性能瓶颈,可考虑异步通知(如事件总线)。
观察者模式参与者角色划分
观察者模式(Observer Pattern)是一种行为设计模式,用于定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。为了更好地理解观察者模式,我们需要明确其核心参与者角色划分。
1. Subject(主题/被观察者)
- 定义:Subject 是被观察的对象,它维护一组观察者(Observer),并提供添加(attach)、删除(detach)和通知(notify)观察者的方法。
- 职责:
- 维护观察者列表(List of Observers)。
- 提供注册(attach)和注销(detach)观察者的接口。
- 当状态发生变化时,调用
notify
方法通知所有观察者。
- 示例代码片段:
public interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers(); }
2. ConcreteSubject(具体主题/具体被观察者)
- 定义:ConcreteSubject 是 Subject 的具体实现类,负责维护自身的状态(state),并在状态变化时通知观察者。
- 职责:
- 存储具体状态(state)。
- 当状态发生变化时,调用父类(或接口)的
notify
方法。
- 示例代码片段:
public class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; notifyObservers(); // 状态变化时通知观察者 } @Override public void attach(Observer observer) { observers.add(observer); } @Override public void detach(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(); } } }
3. Observer(观察者)
- 定义:Observer 是一个接口或抽象类,定义了观察者的更新方法(
update
),用于接收 Subject 的通知。 - 职责:
- 定义统一的更新接口,供 Subject 调用。
- 示例代码片段:
public interface Observer { void update(); }
4. ConcreteObserver(具体观察者)
- 定义:ConcreteObserver 是 Observer 的具体实现类,负责在收到通知后执行具体的更新逻辑。
- 职责:
- 实现
update
方法,定义具体的响应行为。 - 通常需要持有对 Subject 的引用(以便获取 Subject 的状态)。
- 实现
- 示例代码片段:
public class ConcreteObserver implements Observer { private ConcreteSubject subject; public ConcreteObserver(ConcreteSubject subject) { this.subject = subject; subject.attach(this); // 注册到 Subject } @Override public void update() { System.out.println("Observer notified! New state: " + subject.getState()); } }
角色交互流程
- 注册观察者:ConcreteObserver 通过
attach
方法注册到 ConcreteSubject。 - 状态变化:ConcreteSubject 的状态(state)发生变化时,调用
notifyObservers
。 - 通知观察者:ConcreteSubject 遍历观察者列表,调用每个观察者的
update
方法。 - 响应更新:ConcreteObserver 在
update
方法中执行具体逻辑(如打印新状态)。
注意事项
- 避免循环通知:如果观察者在
update
方法中修改 Subject 的状态,可能导致无限循环。 - 线程安全:在多线程环境中,Subject 的观察者列表需要同步(如使用
CopyOnWriteArrayList
)。 - 解耦设计:Subject 和 Observer 应尽量通过接口交互,避免直接依赖具体实现类。
二、模式结构
Subject(主题/被观察者)接口
概念定义
Subject(主题/被观察者)接口是观察者模式中的核心组件之一,它定义了被观察对象的基本行为规范。Subject 负责维护一组观察者(Observer),并在自身状态发生变化时通知所有注册的观察者。
核心职责
- 注册观察者:提供方法允许观察者订阅主题。
- 移除观察者:提供方法允许观察者取消订阅。
- 通知观察者:当主题状态变化时,自动通知所有已注册的观察者。
典型方法定义
public interface Subject {
// 注册观察者
void registerObserver(Observer observer);
// 移除观察者
void removeObserver(Observer observer);
// 通知所有观察者
void notifyObservers();
}
实现要点
- 观察者存储:通常使用集合(如
List<Observer>
)存储观察者对象 - 线程安全:如果存在多线程访问,需要考虑同步机制
- 通知顺序:一般不保证观察者的通知顺序
示例实现
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
public void setState(int newState) {
this.state = newState;
notifyObservers();
}
}
使用场景
- 当一个对象的改变需要同时改变其他对象时
- 当对象需要通知其他对象,但不需要知道这些对象的具体类时
- 跨系统的消息通知场景
注意事项
- 内存泄漏:长期持有观察者引用可能导致内存泄漏,需要及时移除不再需要的观察者
- 性能考虑:观察者数量较多时,通知操作可能影响性能
- 循环依赖:避免观察者和主题之间形成循环通知
- 异常处理:某个观察者的处理失败不应影响其他观察者
与Observer接口的关系
public interface Observer {
void update(int state);
}
Subject 和 Observer 共同构成了观察者模式的基础结构,Subject 负责维护观察者列表并在状态变化时通知它们,而 Observer 则定义接收更新的接口。
ConcreteSubject(具体被观察者)实现
概念定义
ConcreteSubject
是观察者模式中 Subject
(被观察者)接口的具体实现类。它负责维护一个观察者列表,并在自身状态发生变化时通知所有注册的观察者。ConcreteSubject
通常包含以下核心功能:
- 注册(添加)观察者
- 注销(移除)观察者
- 通知观察者状态变化
- 管理被观察的状态数据
核心实现步骤
-
继承或实现 Subject 接口
通常需要实现registerObserver()
、removeObserver()
和notifyObservers()
方法。 -
维护观察者列表
使用集合(如List
或Set
)存储观察者对象。 -
状态管理
定义被观察的状态变量,并提供状态修改方法(触发通知)。 -
通知逻辑
在状态变化时遍历观察者列表,调用其更新方法。
示例代码
import java.util.ArrayList;
import java.util.List;
// 具体被观察者:天气数据站
public class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
public WeatherStation() {
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity);
}
}
// 状态变更方法(触发通知)
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
measurementsChanged();
}
private void measurementsChanged() {
notifyObservers();
}
}
关键注意事项
-
线程安全
如果存在多线程访问,需对观察者列表和状态变量进行同步控制(如使用CopyOnWriteArrayList
)。 -
避免循环通知
确保观察者的更新逻辑不会反向修改ConcreteSubject
状态,导致无限递归。 -
状态一致性
应在状态完全更新后再调用notifyObservers()
(例如上例中先设置temperature/humidity
,再触发通知)。 -
性能优化
对于高频更新的场景,可考虑:- 合并多次通知(如使用
debounce
机制) - 增量通知(仅发送变化的部分状态)
- 合并多次通知(如使用
典型使用场景
- GUI 事件处理
如按钮点击事件监听。 - 发布-订阅系统
如消息队列中的生产者-消费者模型。 - 监控系统
如服务器资源使用率监控报警。
扩展实现技巧
-
支持推送/拉取模式
// 推送特定数据 void notifyObservers(float temp, float humidity); // 或允许观察者拉取数据 float getTemperature(); float getHumidity();
-
使用 Java 内置类
Java 提供了java.util.Observable
(但因其缺陷,通常建议自定义实现)。 -
泛型支持
public class ConcreteSubject<T> implements Subject<T> { private T state; // ... }
观察者模式中的 Observer(观察者)接口
概念定义
Observer 接口是观察者模式中的核心组件之一,它定义了观察者对象必须实现的方法,以便在被观察对象(Subject)状态发生变化时接收通知。在 Java 中,java.util.Observer
是一个内置的接口(但在 Java 9 后被标记为过时,通常推荐自定义实现)。
核心方法
Observer 接口通常包含以下核心方法:
void update(Observable o, Object arg);
o
: 被观察的对象(Subject),即状态发生变化的对象。arg
: 可选的参数,用于传递额外的信息给观察者。
使用场景
Observer 接口适用于以下场景:
- 事件驱动系统:如 GUI 中的按钮点击事件监听。
- 发布-订阅模型:如消息队列中的消费者订阅主题。
- 状态监控:如监控系统状态变化并触发警报。
示例代码
以下是一个自定义 Observer 接口及其实现的示例:
// 自定义 Observer 接口
public interface Observer {
void update(String message);
}
// 具体观察者实现
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " 收到消息: " + message);
}
}
// 被观察者(Subject)
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer1 = new ConcreteObserver("观察者1");
Observer observer2 = new ConcreteObserver("观察者2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers("状态已更新!");
}
}
常见误区与注意事项
- 内存泄漏:如果观察者未正确注销,可能导致内存泄漏(如观察者生命周期长于被观察者)。
- 性能问题:大量观察者或频繁通知可能影响性能,需考虑异步通知或优化逻辑。
- 线程安全:在多线程环境中,需确保观察者的注册、注销和通知操作是线程安全的。
替代方案(Java 9+)
由于 java.util.Observer
已过时,推荐以下替代方案:
- 自定义接口:如示例中的
Observer
接口。 PropertyChangeListener
:用于 JavaBean 的属性变更监听。- 响应式编程库:如 RxJava、Project Reactor 等。
通过实现 Observer 接口,可以轻松实现对象间的松耦合通信,是观察者模式的关键组成部分。
ConcreteObserver(具体观察者)实现
概念定义
ConcreteObserver 是观察者模式中的具体观察者实现类,负责实现 Observer 接口(或继承抽象观察者类),定义当主题(Subject)状态发生变化时需要执行的具体响应逻辑。它是观察者模式中实际处理业务逻辑的组件。
核心职责
- 绑定主题:通过构造函数或 setter 方法关联被观察的 Subject
- 实现更新方法:重写
update()
方法定义具体响应行为 - 状态同步:从 Subject 主动拉取或被动接收最新状态数据
典型实现代码示例(Java)
// 假设已定义 Subject 接口和 Observer 接口
public class ConcreteObserver implements Observer {
private String observerState; // 观察者自身状态
private final Subject subject; // 关联的主题对象
// 构造函数注入主题
public ConcreteObserver(Subject subject) {
this.subject = subject;
subject.registerObserver(this); // 自动注册到主题
}
@Override
public void update() {
// 方式1:拉模型(主动从主题获取数据)
this.observerState = subject.getState();
System.out.println("观察者状态更新为:" + observerState);
// 方式2:推模型(通过参数接收数据)
// update(String newState) 的实现方式
}
// 可选的其他业务方法
public void doSomething() {
System.out.println("基于状态[" + observerState + "]执行业务操作");
}
}
实现方式对比
实现方式 | 特点 | 适用场景 |
---|---|---|
拉模型 | 观察者主动调用 Subject 的 getter 方法获取数据 | 需要灵活选择获取哪些数据时 |
推模型 | Subject 直接将变更数据作为参数传递给 update() | 需要精确控制数据传递时 |
混合模型 | 部分数据通过参数传递,其余数据通过 getter 获取 | 复杂业务场景 |
关键实现要点
-
线程安全:
// 使用 volatile 或同步块保证状态可见性 private volatile String observerState; @Override public synchronized void update() { // 同步方法保证原子性 }
-
防止内存泄漏:
public void unregister() { subject.removeObserver(this); // 必须提供注销方法 }
-
差异化处理:
@Override public void update(String eventType, Object data) { switch(eventType) { case "TYPE_A": handleTypeA(data); break; case "TYPE_B": handleTypeB(data); break; } }
实际应用示例(天气预报订阅)
public class UserDevice implements WeatherObserver {
private String deviceId;
private WeatherData currentWeather;
public UserDevice(String id, WeatherStation station) {
this.deviceId = id;
station.addObserver(this);
}
@Override
public void update(WeatherData data) {
this.currentWeather = data;
display();
}
private void display() {
System.out.printf("设备 %s 显示: 温度 %.1f℃ 湿度 %d%%\n",
deviceId, currentWeather.getTemperature(), currentWeather.getHumidity());
}
}
注意事项
- 避免循环调用:不要在 update() 方法中反向修改 Subject 状态
- 性能优化:对于高频更新场景,考虑使用批处理或异步处理
- 状态一致性:确保在 update() 方法执行期间保持对象状态一致
- 异常处理:单个观察者异常不应影响其他观察者的通知流程
扩展实现技巧
-
使用函数式接口(Java 8+):
public class LambdaObserver { private Consumer<State> updateHandler; public LambdaObserver(Consumer<State> handler) { this.updateHandler = handler; } public void update(State newState) { updateHandler.accept(newState); } }
-
支持优先级:
public class PriorityObserver implements Observer, Comparable<PriorityObserver> { private final int priority; @Override public int compareTo(PriorityObserver o) { return Integer.compare(priority, o.priority); } }
三、实现方式
推模型实现
概念定义
推模型(Push Model)是观察者模式的一种实现方式,指的是当被观察者(Subject)状态发生变化时,主动将详细数据推送给所有观察者(Observer)。与拉模型(Pull Model)不同,观察者无需主动查询被观察者的状态变化细节。
核心特点
- 数据主动推送:被观察者直接将变化后的完整数据发送给观察者
- 观察者被动接收:观察者只需实现接收方法,无需主动获取数据
- 信息传递完整:通常包含状态变化的完整上下文信息
典型实现结构
// 被观察者接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(Object data); // 关键区别:携带数据参数
}
// 观察者接口
interface Observer {
void update(Object data); // 接收被推送的数据
}
// 具体被观察者
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o) {
observers.add(o);
}
public void notifyObservers(Object data) {
for (Observer o : observers) {
o.update(data); // 推送数据给所有观察者
}
}
}
使用场景
- 实时数据监控:如股票价格变动时推送最新行情
- 事件驱动系统:如GUI中的按钮点击事件
- 传感器数据采集:物联网设备状态变化时推送读数
- 消息广播系统:聊天室中的消息推送
代码示例
// 气象站示例(推模型)
class WeatherStation implements Subject {
private float temperature;
private List<Observer> displays = new ArrayList<>();
public void setMeasurements(float temp) {
this.temperature = temp;
notifyObservers(temp); // 推送温度数据
}
// 实现Subject接口方法...
}
class PhoneDisplay implements Observer {
public void update(Object data) {
float temp = (float) data; // 直接使用推送的数据
System.out.println("手机显示温度: " + temp);
}
}
// 使用示例
WeatherStation station = new WeatherStation();
station.registerObserver(new PhoneDisplay());
station.setMeasurements(25.5f); // 自动触发推送
注意事项
- 数据一致性:确保推送时数据已经是完整更新后的状态
- 性能考虑:当观察者数量多或数据量大时,可能产生性能问题
- 类型安全:需要处理好Object类型转换(Java中可用泛型改进)
- 观察者处理能力:被观察者不应假设观察者能立即处理所有推送
与拉模型的对比
特性 | 推模型 | 拉模型 |
---|---|---|
数据获取方式 | 被观察者主动推送完整数据 | 观察者主动请求所需数据 |
耦合度 | 较高(需要知道观察者需要哪些数据) | 较低(观察者自行决定获取什么数据) |
网络带宽消耗 | 可能较大(推送不必要的数据) | 更节约(按需获取) |
实时性 | 更高(立即推送) | 相对较低(存在请求延迟) |
实现复杂度 | 观察者实现简单 | 被观察者实现简单 |
改进建议(Java泛型版)
interface Subject<T> {
void notifyObservers(T data);
}
interface Observer<T> {
void update(T data);
}
// 使用时指定具体类型
class WeatherStation implements Subject<Float> {
void notifyObservers(Float temp) {...}
}
class PhoneDisplay implements Observer<Float> {
void update(Float temp) {...}
}
拉模型实现
概念定义
拉模型(Pull Model)是观察者模式的一种实现方式,其核心特点是:
- 主动权在观察者:观察者主动从被观察者(主题)中"拉取"所需数据
- 按需获取:观察者可以只获取自己关心的数据
- 数据控制:被观察者只需维护状态,不关心观察者如何使用数据
与推模型的区别
特性 | 拉模型 | 推模型 |
---|---|---|
数据传递方向 | 观察者主动获取 | 被观察者主动推送 |
耦合度 | 较低(观察者决定获取什么数据) | 较高(被观察者决定推送什么数据) |
灵活性 | 高(不同观察者可获取不同数据) | 低(所有观察者接收相同数据) |
性能 | 可能较低(多次请求) | 较高(单次推送) |
实现步骤
- 定义主题接口(包含注册、删除、通知观察者的方法)
- 定义观察者接口(包含更新方法)
- 具体主题实现状态维护和通知功能
- 具体观察者实现数据拉取逻辑
示例代码
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(Subject subject); // 通过参数获取主题引用
}
// 具体主题
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
public void setMeasurements(float temp, float humidity) {
this.temperature = temp;
this.humidity = humidity;
notifyObservers();
}
// 观察者通过这些方法拉取数据
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(this); // 将自身引用传递给观察者
}
}
}
// 具体观察者
class TemperatureDisplay implements Observer {
@Override
public void update(Subject subject) {
if (subject instanceof WeatherStation) {
WeatherStation ws = (WeatherStation) subject;
float temp = ws.getTemperature(); // 主动拉取温度数据
System.out.println("温度显示器: " + temp + "°C");
}
}
}
class HumidityDisplay implements Observer {
@Override
public void update(Subject subject) {
if (subject instanceof WeatherStation) {
WeatherStation ws = (WeatherStation) subject;
float humidity = ws.getHumidity(); // 主动拉取湿度数据
System.out.println("湿度显示器: " + humidity + "%");
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
station.registerObserver(new TemperatureDisplay());
station.registerObserver(new HumidityDisplay());
station.setMeasurements(25.5f, 65.0f);
}
}
使用场景
- 观察者需要不同数据子集:当不同观察者只关心主题的部分数据时
- 数据量较大:避免推送不必要的大数据量
- 观察者处理能力不同:允许观察者按自身节奏处理数据
注意事项
- 性能考虑:频繁的拉取操作可能导致性能问题
- 数据一致性:确保观察者拉取数据时主题状态未被修改
- 循环依赖:避免观察者在update()方法中又触发主题状态变更
- 类型安全:需要观察者自行检查主题具体类型(如示例中的instanceof检查)
优化建议
- 为主题接口添加泛型支持,避免类型转换
interface Subject<T> { void registerObserver(Observer<T> o); // ... } interface Observer<T> { void update(T subject); }
- 对频繁拉取的数据提供缓存机制
- 考虑使用CopyOnWriteArrayList等线程安全集合处理观察者列表
基于Java内置Observable类的实现
概念定义
Java内置的Observable
类(位于java.util
包)是观察者模式的官方实现基类。它提供了一套完整的机制,允许被观察对象(Subject)维护一组观察者(Observer),并在状态变化时自动通知它们。
核心方法
方法名 | 作用 |
---|---|
addObserver(Observer o) | 注册观察者 |
deleteObserver(Observer o) | 移除观察者 |
notifyObservers() | 通知所有观察者(不传参) |
notifyObservers(Object arg) | 带数据通知观察者 |
setChanged() | 标记状态已改变(必须调用) |
实现步骤
- 继承
Observable
类创建被观察者 - 实现
Observer
接口创建观察者 - 通过
addObserver()
建立订阅关系 - 被观察者调用
setChanged()
后触发通知
示例代码
// 被观察者(气象站)
class WeatherStation extends Observable {
private float temperature;
public void setTemperature(float temp) {
this.temperature = temp;
setChanged(); // 必须调用!
notifyObservers(temp); // 推送温度数据
}
}
// 观察者(显示设备)
class DisplayDevice implements Observer {
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Float) {
System.out.println("温度更新: " + arg + "℃");
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
DisplayDevice phoneDisplay = new DisplayDevice();
station.addObserver(phoneDisplay);
station.setTemperature(26.5f); // 自动触发phoneDisplay.update()
}
}
注意事项
- 必须调用setChanged():否则
notifyObservers()
不会生效 - 线程安全问题:
Observable
的方法都是同步的(synchronized) - 推拉模式选择:
- 推模式:通过
notifyObservers(Object arg)
传递数据 - 拉模式:观察者通过
Observable o
参数主动获取数据
- 推模式:通过
- 已过时提醒:Java 9+标记为
@Deprecated
,建议改用PropertyChangeListener
等新机制
典型应用场景
- GUI事件处理(如按钮点击监听)
- 传感器数据监控系统
- 股票价格变动通知
- 游戏中的成就系统触发
与自定义实现的对比
特性 | Java Observable | 自定义实现 |
---|---|---|
线程安全 | 内置同步 | 需自行处理 |
灵活性 | 有限(类继承) | 更高(接口实现) |
数据传递 | 支持Object泛型 | 可类型安全 |
生命周期管理 | 自动清理 | 需手动维护 |
自定义接口的实现方式
在 Java 中,接口(interface
)定义了一组抽象方法,而实现接口的类需要提供这些方法的具体实现。自定义接口的实现方式有多种,具体取决于需求场景。
1. 类实现接口
最基础的方式是通过类直接实现接口,使用 implements
关键字。实现类必须重写接口中的所有抽象方法。
// 定义接口
interface Greeting {
void sayHello();
}
// 类实现接口
class EnglishGreeting implements Greeting {
@Override
public void sayHello() {
System.out.println("Hello!");
}
}
public class Main {
public static void main(String[] args) {
Greeting greeting = new EnglishGreeting();
greeting.sayHello(); // 输出: Hello!
}
}
2. 匿名内部类实现接口
适用于临时或一次性使用的接口实现,无需显式定义类。
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
// 匿名内部类实现接口
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from anonymous class!");
}
};
greeting.sayHello(); // 输出: Hello from anonymous class!
}
}
3. Lambda 表达式实现接口
适用于函数式接口(仅含一个抽象方法的接口),语法更简洁。
@FunctionalInterface
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
// Lambda 表达式实现接口
Greeting greeting = () -> System.out.println("Hello from Lambda!");
greeting.sayHello(); // 输出: Hello from Lambda!
}
}
4. 默认方法(Java 8+)
接口可以包含默认方法(default
修饰),实现类无需重写默认方法,但可以按需覆盖。
interface Greeting {
void sayHello();
default void sayBye() {
System.out.println("Goodbye!");
}
}
class EnglishGreeting implements Greeting {
@Override
public void sayHello() {
System.out.println("Hello!");
}
}
public class Main {
public static void main(String[] args) {
EnglishGreeting greeting = new EnglishGreeting();
greeting.sayHello(); // 输出: Hello!
greeting.sayBye(); // 输出: Goodbye!
}
}
5. 静态方法(Java 8+)
接口可以定义静态方法,通过接口名直接调用,无需实现类。
interface Greeting {
static void greet() {
System.out.println("Static greeting!");
}
}
public class Main {
public static void main(String[] args) {
Greeting.greet(); // 输出: Static greeting!
}
}
6. 多接口实现
一个类可以实现多个接口,解决 Java 单继承的限制。
interface Greeting {
void sayHello();
}
interface Farewell {
void sayBye();
}
class MultiLanguageGreeting implements Greeting, Farewell {
@Override
public void sayHello() {
System.out.println("Hello!");
}
@Override
public void sayBye() {
System.out.println("Goodbye!");
}
}
public class Main {
public static void main(String[] args) {
MultiLanguageGreeting greeting = new MultiLanguageGreeting();
greeting.sayHello(); // 输出: Hello!
greeting.sayBye(); // 输出: Goodbye!
}
}
注意事项
- 抽象方法必须实现:实现类必须重写接口中所有抽象方法(除非是抽象类)。
- 默认方法冲突:如果类实现了多个接口,且这些接口有相同的默认方法,需显式重写以避免冲突。
- 函数式接口:Lambda 表达式仅适用于单抽象方法的接口(
@FunctionalInterface
)。 - 接口与抽象类:接口侧重行为约定,抽象类可包含状态和部分实现。
四、典型应用场景
观察者模式在GUI事件处理中的应用
概念定义
观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,所有依赖于它的观察者都会得到通知并自动更新。
在GUI(图形用户界面)事件处理中,观察者模式被广泛应用。GUI组件(如按钮、文本框等)作为被观察者(Subject),而事件监听器(Event Listener)则作为观察者(Observer)。当用户与GUI组件交互(如点击按钮)时,组件会通知所有注册的监听器,触发相应的事件处理方法。
使用场景
- 按钮点击事件:当用户点击按钮时,触发相应的动作
- 键盘输入事件:监听键盘按键的按下和释放
- 鼠标移动事件:跟踪鼠标在界面上的移动和点击
- 窗口状态变化:监听窗口的最小化、最大化或关闭操作
- 数据模型变化:当数据模型更新时自动刷新视图
实现方式
在Java中,GUI事件处理通常使用java.awt.event
或javax.swing.event
包中的类和接口实现观察者模式。
示例代码:按钮点击事件
import javax.swing.*;
import java.awt.event.*;
public class ButtonClickExample {
public static void main(String[] args) {
JFrame frame = new JFrame("观察者模式示例");
JButton button = new JButton("点击我");
// 添加观察者(事件监听器)
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了!");
}
});
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
示例代码:自定义观察者模式实现
import java.util.ArrayList;
import java.util.List;
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 具体主题(按钮)
class Button implements Subject {
private List<Observer> observers = new ArrayList<>();
public void click() {
System.out.println("按钮被点击");
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 观察者接口
interface Observer {
void update();
}
// 具体观察者
class ClickListener implements Observer {
@Override
public void update() {
System.out.println("收到按钮点击通知,执行相应操作");
}
}
public class CustomObserverDemo {
public static void main(String[] args) {
Button button = new Button();
button.registerObserver(new ClickListener());
// 模拟按钮点击
button.click();
}
}
常见误区与注意事项
- 内存泄漏:忘记移除不再需要的观察者可能导致内存泄漏,特别是在长生命周期的被观察者对象中
- 通知顺序:多个观察者的通知顺序通常是不确定的,不应依赖特定的通知顺序
- 性能问题:当观察者数量很大时,通知所有观察者可能影响性能
- 线程安全:在多线程环境中,需要确保观察者的注册、移除和通知操作是线程安全的
- 过度使用:不是所有GUI交互都需要使用观察者模式,简单的回调可能更合适
Java内置支持
Java为GUI事件处理提供了更高级的抽象:
- EventListenerList:Swing中用于管理监听器的专用类
- PropertyChangeSupport:用于实现属性变更通知
- SwingWorker:处理后台任务与GUI更新的交互
这些内置机制都基于观察者模式的思想,但提供了更完善的功能和更好的性能。
发布-订阅系统
概念定义
发布-订阅系统(Publish-Subscribe System)是一种消息传递模式,属于观察者模式的扩展实现。在该系统中,消息的发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是将消息分类到不同的主题(Topic)中。订阅者可以根据自己的需求订阅一个或多个主题,并在发布者发布相关主题的消息时自动接收通知。
核心组件
- 发布者(Publisher):负责将消息发布到特定主题。
- 订阅者(Subscriber):通过订阅主题来接收感兴趣的消息。
- 消息代理(Broker):作为中间件,负责管理主题、存储消息,并将消息从发布者路由到订阅者。
- 主题(Topic):消息的分类标识,订阅者通过订阅主题来过滤消息。
使用场景
- 事件驱动架构:如用户注册后触发邮件通知、日志记录等。
- 实时数据处理:如股票行情推送、IoT设备数据监控。
- 微服务通信:解耦服务间的直接依赖,通过消息传递实现异步通信。
- 日志与监控系统:多个子系统订阅日志主题进行集中分析。
实现方式(代码示例)
以下是一个简单的Java实现,使用内置的java.util.Observable
和Observer
接口(注:Java 9后已过时,此处仅作演示):
import java.util.Observable;
import java.util.Observer;
// 主题(发布者)
class NewsPublisher extends Observable {
private String news;
public void setNews(String topic, String news) {
this.news = topic + ": " + news;
setChanged(); // 标记状态已改变
notifyObservers(this.news); // 通知所有订阅者
}
}
// 订阅者
class EmailSubscriber implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Email Received: " + arg);
}
}
class SMSSubscriber implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("SMS Received: " + arg);
}
}
// 使用示例
public class PubSubDemo {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
publisher.addObserver(new EmailSubscriber());
publisher.addObserver(new SMSSubscriber());
publisher.setNews("Sports", "Team wins championship!");
}
}
常见误区与注意事项
- 消息顺序问题:分布式系统中无法严格保证消息顺序,需业务层处理。
- 重复消费:网络问题可能导致订阅者多次收到同一消息,需实现幂等性。
- 主题设计过度细化:过多主题会增加系统复杂度,建议按业务领域划分。
- 订阅者性能瓶颈:慢消费者可能阻塞消息队列,需采用异步处理或背压机制。
进阶实现(推荐工具)
- Spring Event:基于Spring框架的轻量级发布-订阅实现。
// 发布者 @Autowired private ApplicationEventPublisher publisher; publisher.publishEvent(new CustomEvent(data)); // 订阅者 @EventListener public void handleEvent(CustomEvent event) { ... }
- 消息中间件:如Kafka、RabbitMQ等,支持分布式、持久化的消息传递。
与观察者模式的区别
- 耦合度:观察者模式中主体直接维护观察者列表,发布-订阅通过代理解耦。
- 灵活性:发布-订阅支持动态主题和跨进程通信,观察者模式通常限于单进程。
观察者模式:监控系统状态变化
概念定义
观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象(称为被观察者或主题)的状态发生改变时,所有依赖于它的对象(称为观察者)都会自动收到通知并更新。
使用场景
观察者模式特别适用于以下场景:
- 系统状态监控:当需要监控某个核心系统的状态变化(如服务器健康状态、传感器数据等)时,观察者模式可以实时通知所有依赖组件。
- 事件驱动架构:如GUI框架中的按钮点击事件、消息队列的消费者订阅等。
- 数据同步:当多个显示视图(如仪表盘、日志面板)需要同步展示同一数据源的实时变化时。
核心角色
- Subject(主题)
维护观察者列表,提供注册(attach
)和注销(detach
)方法,以及通知方法(notifyObservers
)。 - Observer(观察者)
定义更新接口(通常为update
方法),供主题状态变化时调用。
Java 实现示例
import java.util.ArrayList;
import java.util.List;
// 主题接口
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// 具体主题(被监控的系统)
class SystemMonitor implements Subject {
private List<Observer> observers = new ArrayList<>();
private String systemStatus;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(systemStatus);
}
}
public void setSystemStatus(String status) {
this.systemStatus = status;
notifyObservers(); // 状态变化时自动通知观察者
}
}
// 观察者接口
interface Observer {
void update(String status);
}
// 具体观察者:日志记录器
class Logger implements Observer {
@Override
public void update(String status) {
System.out.println("[Logger] 系统状态更新为: " + status);
}
}
// 具体观察者:警报器
class Alert implements Observer {
@Override
public void update(String status) {
if ("CRITICAL".equals(status)) {
System.out.println("[Alert] 警告!系统进入紧急状态!");
}
}
}
// 使用示例
public class Demo {
public static void main(String[] args) {
SystemMonitor monitor = new SystemMonitor();
monitor.attach(new Logger());
monitor.attach(new Alert());
// 模拟状态变化
monitor.setSystemStatus("NORMAL");
monitor.setSystemStatus("CRITICAL");
}
}
输出结果
[Logger] 系统状态更新为: NORMAL
[Logger] 系统状态更新为: CRITICAL
[Alert] 警告!系统进入紧急状态!
注意事项
- 避免循环依赖:观察者的更新逻辑不应反向调用主题的方法,否则可能导致无限递归。
- 线程安全:
如果主题和观察者运行在不同线程中,需使用CopyOnWriteArrayList
或同步机制保护观察者列表。 - 性能考虑:
当观察者数量庞大时,批量通知可能成为性能瓶颈,可考虑异步通知(如使用ExecutorService
)。 - 内存泄漏:
及时调用detach()
移除不再需要的观察者,防止因持有引用导致对象无法回收。
常见误区
- 过度通知:在主题状态未实际变化时调用
notifyObservers()
,会导致不必要的性能开销。 - 忽略错误处理:观察者的
update()
方法应捕获自身异常,避免影响其他观察者。
观察者模式中的数据实时更新通知
概念定义
数据实时更新通知是观察者模式的核心功能之一,它允许被观察对象(Subject)在状态变化时自动通知所有注册的观察者(Observers)。这种机制实现了对象间的松耦合通信,确保依赖对象能及时获取最新数据。
实现原理
- 注册机制:观察者通过
attach()
方法订阅主题 - 状态变更检测:主题内部维护状态变更标志
- 通知触发:当调用
notifyObservers()
时,遍历观察者列表 - 推送/拉取模式:
- 推送模式:主题主动发送完整数据
- 拉取模式:观察者收到通知后主动查询
代码实现示例
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 具体主题
class WeatherData implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void setMeasurements(float temp) {
this.temperature = temp;
notifyObservers(); // 数据更新时触发通知
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature); // 推送更新数据
}
}
// 其他接口实现...
}
// 观察者接口
interface Observer {
void update(float temp);
}
// 具体观察者
class Display implements Observer {
@Override
public void update(float temp) {
System.out.println("实时温度更新: " + temp);
}
}
性能优化策略
- 批量通知:对高频更新实现缓冲机制
- 差分更新:仅发送变化的数据部分
- 异步通知:使用线程池处理通知任务
- 优先级队列:重要观察者优先通知
常见应用场景
- 股票价格变动推送
- 物联网设备状态监控
- 聊天消息实时显示
- 仪表盘数据可视化更新
- 多人协作编辑系统
注意事项
- 循环引用:避免观察者同时持有主题引用
- 通知顺序:不要依赖观察者的通知顺序
- 线程安全:多线程环境使用并发集合
- 内存泄漏:及时注销不再需要的观察者
- 性能瓶颈:观察者数量过多时考虑分级通知
高级实现技巧
// 使用Java内置Observerable
class JournalData extends Observable {
void addNews(String news) {
setChanged(); // 标记状态已改变
notifyObservers(news); // 触发通知
}
}
// 使用Java9+的Flow API(响应式流)
class StockPublisher implements Flow.Publisher<Float> {
private final SubmissionPublisher<Float> publisher =
new SubmissionPublisher<>();
void publishPrice(float price) {
publisher.submit(price);
}
}
五、模式优缺点
松耦合的优势
概念定义
松耦合(Loose Coupling)是指系统中各个组件之间的依赖关系较弱,彼此独立性强。在观察者模式中,松耦合体现在主题(Subject)和观察者(Observer)之间通过抽象接口交互,而非直接依赖具体实现类。
核心优势
-
可维护性高
修改一个组件时,无需大规模改动其他组件。例如:观察者逻辑变更时,主题代码无需调整。 -
扩展性强
新增观察者只需实现接口,无需修改主题代码。符合开闭原则(OCP)。 -
复用性提升
主题和观察者可独立复用。例如:同一主题可被不同业务模块的观察者订阅。 -
测试更简单
可对组件进行独立单元测试,通过Mock对象模拟依赖。
观察者模式中的典型体现
// 主题仅依赖Observer接口(松耦合)
public class WeatherStation {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) {
observers.add(o);
}
// 通知时调用接口方法,不关心具体实现
private void notifyObservers() {
for (Observer o : observers) {
o.update(temperature);
}
}
}
// 观察者可以是任何实现类
public class PhoneDisplay implements Observer {
@Override
public void update(float temp) {
System.out.println("手机显示温度: " + temp);
}
}
对比紧耦合的劣势
紧耦合示例:
// 主题直接依赖具体观察者(紧耦合)
public class WeatherStation {
private PhoneDisplay phoneDisplay; // 硬编码依赖
private TVDisplay tvDisplay;
public void notifyDisplays() {
phoneDisplay.show(temp); // 修改观察者需改动此处
tvDisplay.display(temp);
}
}
注意事项
-
性能权衡
松耦合可能增加间接调用(如接口方法调用),但现代JVM优化后影响极小。 -
过度设计风险
简单场景可直接使用紧耦合,如对象生命周期相同且无需扩展时。 -
通信成本
组件间需定义清晰的接口契约,前期设计成本较高。
观察者模式中的广播通信
概念定义
广播通信是观察者模式中的一种消息传递机制,指主题(Subject)状态变化时,向所有已注册的观察者(Observer)发送通知,而不需要知道观察者的具体身份或数量。这种一对多的通信方式实现了松耦合。
核心特点
- 全量通知:主题会通知所有观察者,无论观察者是否需要该事件
- 无差别对待:所有观察者接收相同的通知信息
- 自动传播:新增观察者会自动加入广播接收列表
典型实现方式
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(); // 广播通知方法
}
// 具体主题实现广播
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(this); // 向所有观察者广播
}
}
}
使用场景
- 事件处理系统(如GUI按钮点击事件)
- 消息队列的发布-订阅模型
- 分布式系统中的服务状态通知
- 实时数据监控系统
注意事项
- 性能问题:观察者数量过多时,广播会导致性能下降
- 无效通知:某些观察者可能不关心当前变更
- 顺序依赖:Java默认的观察者通知顺序是未定义的
优化方案
- 引入过滤机制(仅通知感兴趣的观察者)
- 使用异步通知(避免阻塞主题线程)
- 实现优先级机制(控制通知顺序)
广播 vs 定向通知
特性 | 广播通知 | 定向通知 |
---|---|---|
接收对象 | 所有观察者 | 特定观察者 |
耦合度 | 更低 | 更高 |
性能 | 观察者多时较差 | 更高效 |
实现复杂度 | 简单 | 需要维护观察者-事件映射关系 |
观察者模式中的意外更新问题
概念定义
意外更新问题(Unintended Update Problem)是指在观察者模式中,由于观察者与被观察者之间的交互不当,导致观察者接收到预期之外的更新通知,进而引发系统状态不一致或性能损耗的现象。
典型场景
- 嵌套通知:被观察者在处理一个观察者的更新时,又触发了另一个状态变更,形成递归通知。
- 冗余通知:被观察者的状态未发生实际变化,但仍触发了通知(如连续多次set相同值)。
- 交叉依赖:多个观察者相互订阅,形成更新环路。
问题示例
// 被观察者
class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public void setState(int newState) {
// 即使newState与当前state相同,仍会触发通知
this.state = newState;
notifyObservers();
}
private void notifyObservers() {
for (Observer o : observers) {
o.update(this); // 可能触发观察者的副作用操作
}
}
}
// 观察者A的update方法可能修改Subject状态
class ObserverA implements Observer {
void update(Subject s) {
s.setState(s.getState() + 1); // 导致嵌套更新
}
}
解决方案
- 状态变更检查:
public void setState(int newState) {
if (this.state != newState) { // 只有实际变化时才通知
this.state = newState;
notifyObservers();
}
}
- 通知防抖:
private boolean notifying = false;
public void setState(int newState) {
this.state = newState;
if (!notifying) {
notifying = true;
try {
notifyObservers();
} finally {
notifying = false;
}
}
}
- 增量通知:
- void update(Subject s);
+ void update(Subject s, PropertyChangeEvent event);
- 异步通知:
private ExecutorService executor = Executors.newSingleThreadExecutor();
void notifyObservers() {
executor.submit(() -> {
for (Observer o : observers) {
o.update(this);
}
});
}
注意事项
- 避免在观察者回调中修改被观察者状态
- 对高频更新场景考虑批量通知机制
- 复杂系统建议使用专业事件总线(如EventBus)
- 注意观察者执行时长对主流程的影响
观察者过多时的性能问题
概念定义
在观察者模式中,当被观察者(Subject)的状态发生变化时,会通知所有注册的观察者(Observer)。如果观察者数量过多,可能会导致以下性能问题:
- 通知时间过长:被观察者需要遍历所有观察者并逐个调用其更新方法,观察者数量越多,通知耗时越长。
- 内存占用高:每个观察者对象都会占用内存,大量观察者可能导致内存压力。
- 线程阻塞风险:如果通知过程是同步的,某些观察者的处理逻辑较慢时,会阻塞后续观察者的通知。
使用场景中的性能隐患
以下场景容易出现观察者过多的问题:
- GUI 事件监听:如按钮点击事件可能注册了大量监听器。
- 分布式系统:一个服务状态变化需要通知大量订阅者。
- 游戏开发:一个游戏实体状态变化可能被多个系统(如AI、渲染、物理)监听。
解决方案
1. 异步通知
将同步通知改为异步方式,避免阻塞主线程。
// 使用线程池异步通知观察者
ExecutorService executor = Executors.newFixedThreadPool(4);
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> observer.update());
}
}
2. 观察者分组
根据业务场景对观察者分组,只通知相关的观察者。
Map<String, List<Observer>> observerGroups = new HashMap<>();
public void notifyGroup(String group) {
for (Observer observer : observerGroups.get(group)) {
observer.update();
}
}
3. 合并通知
当状态频繁变化时,可以合并多次通知为一次。
private boolean dirty = false;
public void setState() {
// 标记状态变化,但不立即通知
dirty = true;
}
// 定期检查并通知
public void checkAndNotify() {
if (dirty) {
notifyObservers();
dirty = false;
}
}
4. 弱引用观察者
使用 WeakReference 防止观察者无法被回收。
List<WeakReference<Observer>> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(new WeakReference<>(observer));
}
public void notifyObservers() {
Iterator<WeakReference<Observer>> it = observers.iterator();
while (it.hasNext()) {
Observer observer = it.next().get();
if (observer != null) {
observer.update();
} else {
it.remove(); // 清理已被GC的观察者
}
}
}
注意事项
- 线程安全:异步通知时要注意观察者列表的线程安全。
- 通知顺序:异步通知会丢失观察者的执行顺序保证。
- 资源清理:及时移除不再需要的观察者,避免内存泄漏。
- 异常处理:某个观察者的异常不应影响其他观察者的通知。
性能优化对比
方案 | 优点 | 缺点 |
---|---|---|
同步通知 | 实现简单,顺序保证 | 性能差,阻塞严重 |
异步通知 | 响应快,不阻塞 | 顺序无法保证,实现复杂 |
分组通知 | 减少通知数量 | 需要合理分组逻辑 |
合并通知 | 减少通知次数 | 实时性降低 |
六、Java中的实现示例
自定义接口实现案例
什么是自定义接口实现?
自定义接口实现是指开发者根据业务需求,自行定义接口并编写具体的实现类。接口定义了一组方法规范,而实现类则负责具体的逻辑实现。这种方式可以提高代码的灵活性和可扩展性。
使用场景
- 多态需求:当需要同一接口在不同场景下有不同实现时。
- 解耦:将接口定义与具体实现分离,降低模块间的耦合度。
- 扩展性:后续新增功能时,只需新增实现类,无需修改原有代码。
实现步骤
- 定义接口
- 编写实现类
- 使用接口引用调用具体实现
示例代码
// 1. 定义接口
public interface MessageSender {
void sendMessage(String message);
}
// 2. 实现类1:邮件发送
public class EmailSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println("发送邮件:" + message);
}
}
// 2. 实现类2:短信发送
public class SmsSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println("发送短信:" + message);
}
}
// 3. 使用接口
public class Main {
public static void main(String[] args) {
MessageSender sender1 = new EmailSender();
MessageSender sender2 = new SmsSender();
sender1.sendMessage("您的订单已发货");
sender2.sendMessage("验证码:123456");
}
}
注意事项
- 接口方法必须全部实现:实现类必须实现接口中声明的所有方法
- @Override注解:建议在实现方法上添加@Override注解,帮助编译器检查
- 单一职责原则:每个实现类应该只负责一种具体的功能实现
- 接口命名规范:通常以"-able"或"er"结尾,如Runnable、Comparator
高级用法
- 默认方法:Java 8+允许接口包含默认实现
public interface MessageSender {
void sendMessage(String message);
default void log(String msg) {
System.out.println("日志:" + msg);
}
}
- 多接口实现:一个类可以实现多个接口
public class MultiSender implements MessageSender, Logger {
// 需要实现两个接口的所有方法
}
- 工厂模式结合:通过工厂类返回不同的接口实现
public class SenderFactory {
public static MessageSender getSender(String type) {
switch(type) {
case "email": return new EmailSender();
case "sms": return new SmsSender();
default: throw new IllegalArgumentException();
}
}
}
使用 java.util.Observable
概念定义
java.util.Observable
是 Java 提供的一个抽象类,用于实现观察者模式(Observer Pattern)。它允许对象(称为“被观察者”或“主题”)维护一组依赖它的对象(称为“观察者”),并在状态发生变化时自动通知这些观察者。
核心方法
addObserver(Observer o)
注册一个观察者到被观察者的列表中。deleteObserver(Observer o)
从被观察者的列表中移除指定的观察者。notifyObservers()
和notifyObservers(Object arg)
通知所有已注册的观察者,可以传递一个可选参数arg
作为通知的附加信息。setChanged()
标记被观察者的状态已发生变化(必须调用此方法后,notifyObservers
才会生效)。
使用场景
- 事件驱动系统:如 GUI 按钮点击事件、数据变更通知。
- 发布-订阅模型:如消息队列、实时数据推送。
- 解耦业务逻辑:将核心逻辑与后续处理分离(如日志记录、数据统计)。
示例代码
import java.util.Observable;
import java.util.Observer;
// 被观察者(主题)
class WeatherStation extends Observable {
private float temperature;
public void setTemperature(float temperature) {
this.temperature = temperature;
setChanged(); // 标记状态变化
notifyObservers(temperature); // 通知观察者
}
}
// 观察者
class Display implements Observer {
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Float) {
System.out.println("温度更新: " + arg + "°C");
}
}
}
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
Display display = new Display();
station.addObserver(display); // 注册观察者
station.setTemperature(25.5f); // 触发通知
}
}
常见误区
- 忘记调用
setChanged()
如果不调用此方法,notifyObservers()
不会触发观察者的update
方法。 - 线程安全问题
Observable
的方法是非线程安全的,需自行加锁(如synchronized
)。 - 过度使用
对于复杂场景,建议使用更现代的框架(如java.beans.PropertyChangeSupport
或 ReactiveX)。
注意事项
Observable
是一个类而非接口,限制了被观察者的继承灵活性。- Java 9 后已标记为过时(Deprecated),推荐使用
PropertyChangeListener
或第三方库(如 Guava 的EventBus
)。
Spring框架中的观察者模式应用
观察者模式在Spring中的实现方式
Spring框架通过ApplicationEvent
和ApplicationListener
接口实现了观察者模式:
- 事件类:继承
ApplicationEvent
- 监听器:实现
ApplicationListener
接口或使用@EventListener
注解 - 事件发布者:通过
ApplicationEventPublisher
发布事件
核心组件
- ApplicationEvent:所有Spring事件的基类
- ApplicationListener:事件监听器接口
- ApplicationEventPublisher:事件发布接口
- ApplicationEventMulticaster:事件广播器
使用示例
1. 定义自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private String orderId;
public OrderCreatedEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
}
2. 创建事件监听器
// 方式1:实现ApplicationListener接口
@Component
public class OrderCreatedListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.println("Received order created event - Order ID: " + event.getOrderId());
}
}
// 方式2:使用@EventListener注解
@Component
public class AnnotationBasedListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("Annotation-based listener - Order ID: " + event.getOrderId());
}
}
3. 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(String orderId) {
// 业务逻辑...
// 发布事件
eventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));
}
}
Spring内置事件
Spring框架本身也使用观察者模式发布一些内置事件:
ContextRefreshedEvent
:ApplicationContext初始化或刷新时触发ContextStartedEvent
:ApplicationContext启动时触发ContextStoppedEvent
:ApplicationContext停止时触发ContextClosedEvent
:ApplicationContext关闭时触发
异步事件处理
可以通过@Async
注解实现异步事件处理:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
}
@Component
public class AsyncEventListener {
@Async
@EventListener
public void handleAsyncEvent(OrderCreatedEvent event) {
// 异步处理逻辑
}
}
事务绑定事件
Spring 4.2+支持事务绑定事件,使用@TransactionalEventListener
:
@Component
public class TransactionalEventListenerExample {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(OrderCreatedEvent event) {
// 只在事务提交后执行
}
}
注意事项
- 默认情况下,Spring事件是同步处理的
- 监听器的执行顺序可以通过
@Order
注解控制 - 避免在监听器中执行耗时操作,以免阻塞主流程
- 对于需要保证顺序的事件处理,考虑使用同步方式
- 注意事件对象的生命周期,避免内存泄漏
JDK内置观察者模式示例
概念定义
JDK内置的观察者模式实现位于java.util
包中,包含两个核心类:
Observable
(被观察者/主题)Observer
(观察者接口)
这是Java原生提供的观察者模式实现方案,开发者可以直接继承/实现这些类来快速构建观察者模式应用。
核心类详解
Observable类
public class Observable {
private boolean changed = false;
private Vector<Observer> observers = new Vector<>();
public synchronized void addObserver(Observer o) {
if (!observers.contains(o)) {
observers.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
observers.removeElement(o);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) return;
arrLocal = observers.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
protected synchronized void setChanged() {
changed = true;
}
}
Observer接口
public interface Observer {
void update(Observable o, Object arg);
}
完整使用示例
import java.util.Observable;
import java.util.Observer;
// 被观察者(气象站)
class WeatherStation extends Observable {
private float temperature;
public void setTemperature(float temp) {
this.temperature = temp;
setChanged(); // 标记状态已改变
notifyObservers(temp); // 通知观察者
}
}
// 观察者(显示设备)
class DisplayDevice implements Observer {
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherStation) {
System.out.println("温度更新: " + arg + "°C");
}
}
}
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
DisplayDevice device = new DisplayDevice();
station.addObserver(device);
station.setTemperature(25.5f); // 输出:温度更新: 25.5°C
station.setTemperature(26.8f); // 输出:温度更新: 26.8°C
}
}
使用注意事项
-
必须调用setChanged()
通知观察者前必须先调用setChanged()
,否则notifyObservers()
不会生效 -
线程安全问题
Observable
的方法都是同步的(synchronized),但业务逻辑需要自行保证线程安全 -
Vector的使用
内部使用线程安全的Vector
存储观察者,但在高并发场景可能成为性能瓶颈 -
已被标记为过时
自Java 9起被标记为@Deprecated
,建议使用PropertyChangeListener
或其他实现方式
替代方案
由于JDK实现存在局限性,现代开发更推荐:
- 自行实现观察者模式
- 使用
java.beans.PropertyChangeSupport
- 采用响应式编程库(如RxJava、Reactor)
七、模式扩展与变体
中介者模式(Mediator Pattern)
概念定义
中介者模式是一种行为设计模式,用于减少多个对象或类之间的直接通信依赖。它通过引入一个中介者对象来封装一系列对象之间的交互,使得这些对象不需要显式地相互引用,从而降低耦合度。
核心思想
- 解耦对象间的直接交互:对象之间不再直接调用彼此的方法,而是通过中介者进行通信。
- 集中控制逻辑:中介者负责协调对象间的交互,逻辑集中在一个地方,便于维护。
- 多对多转为一对多:原本对象之间可能形成复杂的网状关系,通过中介者转变为星型结构。
使用场景
- 对象间通信复杂:当系统中对象之间存在大量相互调用,导致依赖关系混乱时。
- 需要统一协调行为:例如聊天室(用户不直接互发消息,而是通过服务器转发)、事件调度系统。
- 避免紧耦合:当需要复用某个对象,但它的依赖关系过于复杂时。
模式结构
- Mediator(中介者接口):定义对象间通信的接口。
- ConcreteMediator(具体中介者):实现中介者接口,协调各对象的行为。
- Colleague(同事类):所有需要交互的对象均继承或实现此抽象类/接口,通过中介者与其他对象通信。
示例代码(Java实现)
// 1. 中介者接口
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
// 2. 具体中介者(聊天室服务器)
class ChatRoom implements Mediator {
@Override
public void sendMessage(String message, Colleague colleague) {
System.out.println(colleague.getName() + " 发送消息: " + message);
}
}
// 3. 同事类抽象
abstract class Colleague {
protected Mediator mediator;
protected String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public String getName() { return name; }
public abstract void send(String message);
}
// 4. 具体同事类(用户)
class User extends Colleague {
public User(Mediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
mediator.sendMessage(message, this);
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
Mediator chatRoom = new ChatRoom();
Colleague user1 = new User(chatRoom, "Alice");
Colleague user2 = new User(chatRoom, "Bob");
user1.send("你好!"); // 输出: Alice 发送消息: 你好!
user2.send("Hi~"); // 输出: Bob 发送消息: Hi~
}
}
常见误区与注意事项
- 中介者可能成为上帝对象:如果中介者承担过多职责,会导致自身难以维护。
- 解决:按功能拆分多个中介者。
- 过度设计:在简单场景中使用中介者模式反而会增加复杂度。
- 适用判断:当对象间交互超过 3 个以上时再考虑。
- 性能问题:所有通信经过中介者,可能成为瓶颈。
- 优化:异步处理或结合观察者模式实现事件总线。
与观察者模式的区别
特性 | 中介者模式 | 观察者模式 |
---|---|---|
交互方向 | 双向(中介者与同事类相互调用) | 单向(主题通知观察者) |
目的 | 集中控制对象间通信 | 实现松耦合的事件通知系统 |
典型场景 | 聊天室、订单处理系统 | 事件监听、数据变更通知 |
实际应用案例
- GUI框架:如Java Swing中,
Dialog
作为中介者协调按钮、输入框等组件。 - 微服务协调:API网关作为中介者处理服务间的请求路由。
- 游戏开发:游戏引擎协调角色、道具、地图等对象的交互。
责任链模式
概念定义
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者能够处理该请求为止。这种模式解耦了请求的发送者和接收者,允许多个对象都有机会处理请求。
核心思想
- 链式处理:将多个处理者组成一条链,请求沿着链传递
- 动态责任分配:可以在运行时动态改变链中的处理者或它们的顺序
- 单一责任原则:每个处理者只关注自己能处理的请求
结构组成
- Handler(抽象处理者):定义处理请求的接口,通常包含处理方法和设置后继者的方法
- ConcreteHandler(具体处理者):实现抽象处理者的具体类,处理它负责的请求,否则将请求转发给后继者
- Client(客户端):创建处理链并向链头的处理者提交请求
使用场景
- 多个对象可以处理同一请求,但具体由哪个对象处理在运行时动态确定
- 需要在不明确指定接收者的情况下,向多个对象中的一个提交请求
- 可动态指定一组对象处理请求
示例代码
// 抽象处理者
abstract class Handler {
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(int request);
}
// 具体处理者A
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(int request) {
if (request <= 10) {
System.out.println("ConcreteHandlerA处理请求:" + request);
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
// 具体处理者B
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(int request) {
if (request > 10 && request <= 20) {
System.out.println("ConcreteHandlerB处理请求:" + request);
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
Handler h1 = new ConcreteHandlerA();
Handler h2 = new ConcreteHandlerB();
h1.setSuccessor(h2);
// 处理请求
int[] requests = {5, 15, 25};
for (int request : requests) {
h1.handleRequest(request);
}
}
}
常见误区与注意事项
- 链的完整性:确保请求最终能被处理,避免请求在链中丢失
- 性能考虑:长链可能导致性能问题,特别是当大多数请求需要遍历整个链时
- 循环引用:注意处理者之间的引用关系,避免形成循环链
- 请求处理确认:明确请求是否已被处理,避免重复处理
变体与扩展
- 纯责任链:请求必须被某个处理者处理
- 不纯责任链:允许请求不被任何处理者处理
- 组合模式结合:可以使用组合模式来构建更复杂的处理链
与其他模式的关系
- 与命令模式:责任链模式可以配合命令模式使用,将请求封装为命令对象
- 与组合模式:可以使用组合模式来构建树形结构的责任链
- 与装饰器模式:两者都基于递归组合,但目的不同(装饰器添加功能,责任链处理请求)
实际应用案例
- Java Servlet中的FilterChain
- Spring Security的过滤器链
- 异常处理机制(try-catch块链)
- 审批流程系统(多级审批)
观察者模式中的异步通知
概念定义
异步通知是指观察者模式中,当被观察者(Subject)状态发生变化时,不是立即同步地通知所有观察者(Observers),而是通过异步机制(如消息队列、线程池等)将通知事件放入队列,由专门的线程或处理器来执行实际的观察者回调。
使用场景
- 高延迟观察者:当某些观察者的处理逻辑耗时较长时,异步通知可以避免阻塞被观察者的主线程。
- 跨系统通知:需要通过网络调用或远程服务通知观察者时,异步机制更可靠。
- 事件风暴场景:当被观察者可能在短时间内触发大量状态变化时,异步队列可以起到缓冲作用。
- 松耦合需求:希望观察者处理逻辑与被观察者完全解耦的场景。
实现方式
1. 线程池实现
// 被观察者
class AsyncSubject {
private ExecutorService executor = Executors.newFixedThreadPool(4);
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) {
observers.add(o);
}
public void changeState() {
// 状态变更逻辑...
notifyObserversAsync();
}
private void notifyObserversAsync() {
for (Observer o : observers) {
executor.submit(() -> o.update(this));
}
}
}
2. 消息队列实现
// 使用BlockingQueue作为简单消息队列
class MessageQueueSubject {
private BlockingQueue<Observer> queue = new LinkedBlockingQueue<>();
private List<Observer> observers = new ArrayList<>();
private Thread consumerThread;
public MessageQueueSubject() {
consumerThread = new Thread(this::consumeMessages);
consumerThread.start();
}
public void addObserver(Observer o) {
observers.add(o);
}
private void consumeMessages() {
while (true) {
try {
Observer o = queue.take();
o.update(this);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
public void changeState() {
// 状态变更逻辑...
for (Observer o : observers) {
queue.offer(o);
}
}
}
注意事项
- 线程安全:确保被观察者状态的线程安全访问
- 顺序保证:异步通知可能无法保证通知到达观察者的顺序
- 错误处理:需要妥善处理观察者处理过程中抛出的异常
- 资源管理:合理配置线程池大小或消息队列容量
- 内存泄漏:长时间运行的异步通知系统需要注意观察者的生命周期管理
高级实现建议
- 使用
CompletableFuture
实现更灵活的异步通知链:
void notifyObservers() {
observers.forEach(o ->
CompletableFuture.runAsync(() -> o.update(this))
.exceptionally(ex -> {
System.err.println("Observer failed: " + ex);
return null;
})
);
}
- 对于分布式系统,可以考虑使用:
- Spring的
ApplicationEvent
+@Async
- 消息中间件(Kafka/RabbitMQ)
- Reactor或RxJava等响应式编程框架
观察者模式在事件驱动架构中的应用
概念定义
观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象(称为"主题")的状态发生改变时,所有依赖于它的对象(称为"观察者")都会自动收到通知并更新。在事件驱动架构中,观察者模式是实现事件发布-订阅机制的核心模式。
使用场景
- GUI事件处理:如按钮点击、鼠标移动等事件的监听
- 消息队列系统:如Kafka、RabbitMQ等消息中间件的消费者订阅
- 微服务间通信:服务间的异步事件通知
- 实时数据推送:如股票价格变动、天气更新等实时数据推送
- 日志系统:多个日志处理器订阅日志事件
实现方式
Java标准库实现
import java.util.Observable;
import java.util.Observer;
// 主题(被观察者)
class EventPublisher extends Observable {
private String eventData;
public void setEvent(String data) {
this.eventData = data;
setChanged(); // 标记状态已改变
notifyObservers(data); // 通知所有观察者
}
}
// 观察者
class EventSubscriber implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("收到事件: " + arg);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
EventPublisher publisher = new EventPublisher();
publisher.addObserver(new EventSubscriber());
publisher.setEvent("测试事件数据");
}
}
自定义实现(更灵活)
import java.util.ArrayList;
import java.util.List;
// 事件接口
interface EventListener {
void onEvent(String eventData);
}
// 事件发布者
class CustomEventPublisher {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void publishEvent(String data) {
for (EventListener listener : listeners) {
listener.onEvent(data);
}
}
}
// 事件订阅者
class CustomEventSubscriber implements EventListener {
@Override
public void onEvent(String eventData) {
System.out.println("处理事件: " + eventData);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
CustomEventPublisher publisher = new CustomEventPublisher();
publisher.addListener(new CustomEventSubscriber());
publisher.publishEvent("自定义事件数据");
}
}
常见误区与注意事项
- 内存泄漏:观察者未正确注销可能导致内存泄漏
- 通知顺序:观察者的通知顺序通常不应影响业务逻辑
- 性能问题:大量观察者可能导致通知过程变慢
- 线程安全:多线程环境下需要保证观察者列表的线程安全
- 过度使用:不是所有场景都需要观察者模式,简单回调可能更合适
在事件驱动架构中的优势
- 松耦合:发布者和订阅者不需要知道彼此的具体实现
- 可扩展性:可以轻松添加新的订阅者而不影响现有系统
- 异步处理:支持事件的异步处理和分发
- 事件溯源:便于实现事件溯源模式
- 反应式编程:是反应式编程的基础模式之一
实际应用示例(Spring事件机制)
// 定义事件
public class CustomEvent extends ApplicationEvent {
private String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
// 事件发布者
@Service
class EventPublisherService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishEvent(String message) {
eventPublisher.publishEvent(new CustomEvent(this, message));
}
}
// 事件监听器
@Component
class EventListenerComponent {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("收到Spring事件: " + event.getMessage());
}
}
性能优化建议
- 使用线程池处理事件通知
- 对高频事件考虑批量处理
- 对不重要的事件使用异步处理
- 考虑使用专门的事件总线框架(如Guava EventBus)
- 对观察者进行分组,实现不同优先级的事件处理
八、与其他模式的关系
观察者模式与发布-订阅模式对比
概念定义
- 观察者模式:一种直接耦合的设计模式,观察者(Observer)直接订阅主题(Subject),主题状态变化时主动通知所有观察者。
- 发布-订阅模式:通过**消息代理(事件通道)**解耦,发布者(Publisher)和订阅者(Subscriber)互不知晓对方存在,由中间件调度消息。
核心差异
特性 | 观察者模式 | 发布-订阅模式 |
---|---|---|
耦合度 | 主题与观察者直接依赖 | 完全解耦(通过中间件) |
通信方式 | 同步调用(如方法通知) | 异步(如消息队列)或同步 |
知晓关系 | 主题知道观察者列表 | 双方仅知道事件通道,互不知晓 |
典型实现 | java.util.Observable | Kafka/RabbitMQ/EventBus |
使用场景
-
观察者模式适用:
- 需要实时响应(如GUI按钮点击事件)
- 观察者数量较少,逻辑简单
- 强一致性要求(如订单状态同步)
-
发布-订阅模式适用:
- 跨系统/模块解耦(如微服务通知)
- 需要削峰填谷(高并发场景)
- 一对多广播(如新闻推送)
代码示例对比
观察者模式实现
// 主题
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) {
observers.add(o);
}
public void notifyAll(String event) {
for (Observer o : observers) {
o.update(event); // 直接调用观察者方法
}
}
}
// 观察者
interface Observer {
void update(String event);
}
发布-订阅模式实现(伪代码)
// 事件总线
class EventBus {
private Map<String, List<Consumer>> subscribers = new HashMap<>();
public void subscribe(String topic, Consumer listener) {
subscribers.computeIfAbsent(topic, k -> new ArrayList<>()).add(listener);
}
public void publish(String topic, String event) {
subscribers.getOrDefault(topic, Collections.emptyList())
.forEach(listener -> listener.accept(event)); // 通过中间层转发
}
}
常见误区
-
认为两者完全等同:
发布-订阅是观察者模式的解耦升级版,但架构复杂度更高。 -
混淆同步/异步:
观察者模式默认同步阻塞,发布-订阅常为异步(但非必然,如EventBus
可同步)。 -
过度设计:
简单场景使用观察者模式更轻量,引入消息中间件可能增加维护成本。
选择建议
-
选择观察者模式当:
✅ 需要简单直接的响应机制
✅ 调用链路可控(避免循环通知) -
选择发布-订阅当:
✅ 需要跨进程/服务通信
✅ 需要流量控制或持久化消息
观察者模式与中介者模式的区别
概念定义
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
- 中介者模式:用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
核心区别
-
交互方式:
- 观察者模式:对象之间通过直接订阅和通知的方式进行交互,发布者(Subject)直接通知订阅者(Observer)。
- 中介者模式:对象之间通过中介者间接通信,所有交互逻辑由中介者集中管理,对象之间不直接交互。
-
耦合性:
- 观察者模式:订阅者和发布者之间存在松散的耦合,但订阅者需要知道发布者的存在。
- 中介者模式:对象之间完全解耦,所有交互逻辑由中介者处理,对象只需与中介者通信。
-
适用场景:
- 观察者模式:适用于一对多的依赖关系,例如事件处理系统、消息通知等。
- 中介者模式:适用于多对多的复杂交互,例如聊天室、UI组件协调等。
-
设计目标:
- 观察者模式:关注的是状态变化的通知。
- 中介者模式:关注的是对象交互的简化。
示例对比
观察者模式示例
// 发布者
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 订阅者
interface Observer {
void update();
}
中介者模式示例
// 中介者
class ChatRoom {
public void sendMessage(User user, String message) {
System.out.println(user.getName() + ": " + message);
}
}
// 用户
class User {
private String name;
private ChatRoom chatRoom;
public User(String name, ChatRoom chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
}
public void send(String message) {
chatRoom.sendMessage(this, message);
}
public String getName() {
return name;
}
}
总结
- 观察者模式:适合处理单向通知的场景,发布者主动通知订阅者。
- 中介者模式:适合处理多对象协作的场景,通过中介者集中管理交互逻辑。
观察者模式与责任链模式的差异
1. 核心目的不同
- 观察者模式:用于建立对象间的一对多依赖关系,当一个对象(被观察者)状态改变时,自动通知所有依赖它的对象(观察者)。
- 责任链模式:用于解耦请求的发送者和接收者,将多个处理器组成链式结构,请求沿链传递直到被处理。
2. 交互方式不同
- 观察者模式:
- 被观察者主动推送通知给所有观察者。
- 观察者通常无法中断通知流程。
- 责任链模式:
- 请求沿处理器链被动传递。
- 每个处理器可决定是否处理请求或传递给下一节点。
3. 参与者关系不同
特性 | 观察者模式 | 责任链模式 |
---|---|---|
参与者关系 | 星型拓扑(中心化) | 线性链式结构(去中心化) |
动态性 | 观察者可动态注册/注销 | 处理器节点可动态重组 |
通知方向 | 单向(被观察者→观察者) | 单向(沿链传递) |
4. 典型应用场景对比
-
观察者模式适用场景:
- GUI事件处理(如按钮点击通知多个监听器)
- 发布-订阅系统
- 数据变化触发联动更新
-
责任链模式适用场景:
- 多级审批流程
- 过滤器链(如Web中间件)
- 异常处理链
5. 代码结构差异示例
// 观察者模式典型结构
interface Observer {
void update(Event event);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
void addObserver(Observer o) {
observers.add(o);
}
void notifyObservers() {
observers.forEach(o -> o.update(event));
}
}
// 责任链模式典型结构
interface Handler {
void handle(Request request);
void setNext(Handler next);
}
class ConcreteHandler implements Handler {
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
public void handle(Request request) {
if (canHandle(request)) {
// 处理逻辑
} else if (next != null) {
next.handle(request);
}
}
}
6. 关键区别总结
维度 | 观察者模式 | 责任链模式 |
---|---|---|
通信方向 | 广播式通知 | 线性传递 |
控制流 | 由被观察者主导 | 由处理器节点控制 |
结果影响 | 所有观察者都会收到通知 | 请求可能被中途拦截 |
耦合度 | 被观察者知晓所有观察者 | 处理器只需知道下一个节点 |
观察者模式与命令模式的协同使用
概念定义
观察者模式(Observer Pattern)和命令模式(Command Pattern)是两种常用的行为设计模式,它们可以协同工作以实现更灵活的解耦设计。
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
- 命令模式:将请求封装为对象,从而允许用户使用不同的请求、队列或日志请求来参数化其他对象。
协同使用的场景
当需要将命令的触发与命令的执行解耦,并且需要动态地通知多个对象时,可以将这两种模式结合使用。例如:
- 事件驱动的系统:命令对象作为事件的封装,观察者监听这些事件并触发相应的操作。
- 撤销/重做功能:命令模式封装操作和状态,观察者模式用于通知界面更新。
- 多级通知机制:命令的执行结果需要通知多个组件时,观察者模式可以扩展通知的灵活性。
实现方式
通常的协同方式是将命令对象作为被观察者(Subject),而具体的执行者或界面组件作为观察者(Observer)。当命令执行完成后,通知所有观察者。
示例代码
以下是一个简单的示例,展示如何将观察者模式和命令模式结合使用:
import java.util.ArrayList;
import java.util.List;
// 观察者接口
interface Observer {
void update(String message);
}
// 被观察者(命令的基类)
abstract class Command {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
protected void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
public abstract void execute();
}
// 具体命令
class ConcreteCommand extends Command {
@Override
public void execute() {
System.out.println("Executing command...");
notifyObservers("Command executed successfully");
}
}
// 具体观察者
class ConcreteObserver implements Observer {
@Override
public void update(String message) {
System.out.println("Observer received: " + message);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Command command = new ConcreteCommand();
Observer observer = new ConcreteObserver();
command.addObserver(observer);
command.execute();
}
}
常见误区与注意事项
- 过度解耦:如果协同使用导致系统过于复杂,可能需要重新评估设计。
- 循环依赖:命令和观察者之间应避免循环通知,否则可能导致无限循环。
- 性能问题:如果观察者数量庞大,频繁的通知可能影响性能,可以考虑异步通知或批量处理。
- 命令的粒度:命令的粒度应适中,过细可能导致观察者处理过于频繁,过粗可能导致观察者难以复用。
优势
- 解耦:命令的触发与执行完全解耦,观察者可以动态增减。
- 扩展性:可以轻松添加新的命令或观察者,无需修改现有代码。
- 灵活性:支持复杂的通知逻辑,如过滤、优先级等。
通过合理结合这两种模式,可以构建出高度灵活且易于维护的事件驱动系统。
九、最佳实践
避免循环依赖
概念定义
循环依赖指的是两个或多个模块、类或组件之间相互依赖,形成一个闭环的依赖关系。例如,类A依赖类B,类B又依赖类A,导致系统无法正确初始化或运行。
使用场景
循环依赖通常出现在以下场景中:
- 模块化设计:多个模块相互调用对方的接口或服务。
- 分层架构:上层模块依赖下层模块,而下层模块又反向依赖上层模块。
- 事件驱动系统:观察者模式中,观察者和被观察者相互引用。
常见误区或注意事项
- 初始化顺序问题:循环依赖可能导致类无法正确初始化,尤其是在依赖注入框架中。
- 代码维护困难:循环依赖会增加代码的耦合性,使得修改或扩展功能变得复杂。
- 测试困难:由于模块之间紧密耦合,单元测试难以独立进行。
解决方法
- 依赖倒置原则(DIP):通过引入接口或抽象类,将具体实现与依赖解耦。
- 事件驱动:使用观察者模式或发布-订阅模式,避免直接依赖。
- 延迟注入:在需要时才注入依赖,而不是在初始化时。
- 重构设计:重新设计模块或类的职责,消除不必要的依赖。
示例代码
以下是一个通过接口解耦循环依赖的示例:
// 定义接口
interface ServiceA {
void doSomething();
}
interface ServiceB {
void doSomethingElse();
}
// 实现类
class ServiceAImpl implements ServiceA {
private ServiceB serviceB;
public ServiceAImpl(ServiceB serviceB) {
this.serviceB = serviceB;
}
@Override
public void doSomething() {
System.out.println("ServiceA is doing something");
serviceB.doSomethingElse();
}
}
class ServiceBImpl implements ServiceB {
private ServiceA serviceA;
public ServiceBImpl(ServiceA serviceA) {
this.serviceA = serviceA;
}
@Override
public void doSomethingElse() {
System.out.println("ServiceB is doing something else");
serviceA.doSomething();
}
}
// 使用依赖注入框架或手动解耦
public class Main {
public static void main(String[] args) {
ServiceA serviceA = new ServiceAImpl(null);
ServiceB serviceB = new ServiceBImpl(serviceA);
((ServiceAImpl) serviceA).setServiceB(serviceB); // 通过setter注入
}
}
其他建议
- 代码审查:定期检查代码中的循环依赖问题。
- 工具支持:使用静态分析工具(如SonarQube)检测循环依赖。
- 设计模式:合理使用设计模式(如中介者模式)来解耦模块。
观察者模式中的异常处理
异常处理的重要性
在观察者模式中,当主题(Subject)状态发生变化时,会通知所有注册的观察者(Observer)。如果某个观察者在处理通知时抛出异常,可能会影响其他观察者的正常执行,甚至导致整个通知过程失败。因此,合理的异常处理机制至关重要。
常见异常处理策略
1. 捕获并记录异常
主题在通知观察者时,可以捕获每个观察者的异常,并记录日志,确保其他观察者仍能正常接收通知。
public class ConcreteSubject extends Subject {
@Override
public void notifyObservers() {
for (Observer observer : observers) {
try {
observer.update();
} catch (Exception e) {
System.err.println("Observer处理异常: " + e.getMessage());
// 记录日志
}
}
}
}
2. 使用责任链模式
将观察者的调用封装为责任链,某个观察者失败后可以选择继续或中断链式调用。
public class ObserverChain {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
try {
observer.update();
} catch (Exception e) {
// 根据业务决定是否继续
if (shouldContinue(e)) {
continue;
}
break;
}
}
}
}
3. 异步通知
通过线程池异步通知观察者,避免某个观察者的异常阻塞其他观察者。
public class AsyncSubject extends Subject {
private ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> {
try {
observer.update();
} catch (Exception e) {
System.err.println("异步处理异常: " + e.getMessage());
}
});
}
}
}
注意事项
- 避免静默吞掉异常:至少要记录日志,否则可能掩盖严重问题。
- 资源清理:如果观察者操作涉及资源(如数据库连接),确保异常时能正确释放。
- 性能影响:过多的异常可能影响系统性能,需监控异常频率。
- 业务一致性:某些业务场景下,需要所有观察者都成功,此时可能需要事务机制。
示例:Spring的事件监听异常处理
Spring框架的事件机制提供了ErrorHandler
接口处理监听器异常:
@Configuration
public class EventConfig {
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setErrorHandler(e -> {
System.err.println("监听器处理异常: " + e.getMessage());
});
return multicaster;
}
}
线程安全实现
概念定义
线程安全指的是在多线程环境下,某个函数、函数库或类能够正确地处理多个线程之间的共享变量,保证程序行为的正确性。简而言之,线程安全的代码在多线程环境中运行时不会出现数据竞争或不一致的情况。
线程安全的实现方式
1. 不可变对象(Immutable Objects)
不可变对象是指一旦创建后其状态不能被修改的对象。由于对象的状态不可变,因此多个线程可以安全地共享该对象,无需额外的同步措施。
示例代码:
public final class ImmutableObject {
private final int value;
public ImmutableObject(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
2. 同步方法(Synchronized Methods)
通过在方法声明中添加 synchronized
关键字,可以确保同一时间只有一个线程能够访问该方法。
示例代码:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
3. 同步代码块(Synchronized Blocks)
与同步方法类似,但可以更细粒度地控制同步范围,仅对需要同步的代码块加锁。
示例代码:
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
4. 使用 volatile
关键字
volatile
关键字用于确保变量的可见性,即当一个线程修改了 volatile
变量的值,其他线程能够立即看到修改后的值。但它不能保证原子性。
示例代码:
public class VolatileExample {
private volatile boolean flag = false;
public void toggleFlag() {
flag = !flag;
}
public boolean isFlag() {
return flag;
}
}
5. 使用 java.util.concurrent
包中的线程安全类
Java 提供了一系列线程安全的集合类和工具类,如 ConcurrentHashMap
、CopyOnWriteArrayList
、AtomicInteger
等。
示例代码:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadSafeExample {
private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
private AtomicInteger counter = new AtomicInteger(0);
public void addToMap(String key, String value) {
map.put(key, value);
}
public void incrementCounter() {
counter.incrementAndGet();
}
}
6. 使用锁(Lock)
Java 提供了 ReentrantLock
等锁机制,可以更灵活地控制同步。
**示例代码:`
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
使用场景
- 不可变对象:适用于共享数据不需要修改的场景,如配置信息。
- 同步方法/代码块:适用于需要对共享资源进行简单同步的场景。
volatile
:适用于单个变量的可见性需求,如标志位。- 线程安全类:适用于需要高性能线程安全集合的场景。
- 锁:适用于需要更复杂同步逻辑的场景,如尝试获取锁、超时等。
常见误区或注意事项
- 过度同步:过多的同步会导致性能下降,应尽量减少同步范围。
- 误用
volatile
:volatile
不能保证复合操作的原子性(如i++
)。 - 死锁:多个线程互相持有对方需要的锁时会导致死锁,应避免嵌套锁。
- 线程安全类的误用:即使使用了线程安全类,复合操作仍需额外同步。例如:
if (!map.containsKey(key)) { map.put(key, value); // 仍然需要同步 }
- 锁的粒度:锁的粒度过大会降低并发性能,过小可能导致数据不一致。
通过合理选择线程安全实现方式,可以确保多线程程序的正确性和性能。
观察者模式的性能优化建议
观察者模式是一种常用的设计模式,但在实际应用中可能会遇到性能问题。以下是一些优化建议:
减少通知频率
- 批量通知:当被观察者状态频繁变化时,可以考虑合并多次变化,进行一次性通知。
- 延迟通知:使用队列或定时器延迟通知,避免高频触发。
优化观察者管理
- 使用高效的数据结构:如
CopyOnWriteArrayList
或ConcurrentHashMap
来存储观察者,提高并发性能。 - 弱引用:使用
WeakReference
存储观察者,避免内存泄漏。
异步处理
- 异步通知:将通知过程放到单独的线程中执行,避免阻塞被观察者。
- 线程池:使用线程池管理通知任务,提高资源利用率。
选择性通知
- 事件过滤:只通知对特定事件感兴趣的观察者。
- 差分更新:仅传递变化的部分数据,减少数据传输量。
示例代码(异步通知优化)
// 使用线程池异步通知观察者
public class AsyncSubject extends Subject {
private ExecutorService executor = Executors.newFixedThreadPool(4);
@Override
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> observer.update(this, arg));
}
}
}
其他优化技巧
- 避免在通知过程中修改观察者列表:这可能导致并发问题。
- 考虑使用响应式编程框架:如 RxJava,它们已经内置了高效的观察者模式实现。
通过以上优化,可以显著提高观察者模式在高并发或高性能场景下的表现。