文章目录
-
- [1. 场景背景](#1. 场景背景)
- [2. 代码实现](#2. 代码实现)
-
- [2.1 抽象层:定义规约](#2.1 抽象层:定义规约)
- [2.2 具体层:业务逻辑实现](#2.2 具体层:业务逻辑实现)
- [2.3 客户端:模拟运行](#2.3 客户端:模拟运行)
- [3. 模式结构解析](#3. 模式结构解析)
-
- [UML 类图](#UML 类图)
- 核心工作流程
- [4. JDK 中的观察者模式](#4. JDK 中的观察者模式)
-
- [4.1 简单的对比](#4.1 简单的对比)
- [4.2 JDK 9 废弃了 Observable](#4.2 JDK 9 废弃了 Observable)
- [4.3 现在的推荐做法](#4.3 现在的推荐做法)
- [5. 总结](#5. 总结)
核心思想:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
在电商系统中,"商品降价通知"是典型的观察者模式应用场景。今天我们以 "华为Mate 60 Pro 价格变动通知会员" 为例,通过 Java 代码手写一个观察者模式,并深入探讨其背后的设计哲学。
1. 场景背景
假设我们经营一家 OnlineStore (在线商店)。
- 被观察者 (Subject):热门商品(如华为 Mate 60 Pro)。
- 观察者 (Observer):关注该商品的会员(张三、李四、王五)。
- 需求:当商品价格发生变动时,系统需自动通知所有关注该商品的会员。
2. 代码实现
我们将代码分为三个部分:接口层、具体实现层、业务测试层。
2.1 抽象层:定义规约
首先,我们需要定义"主题"和"观察者"的接口,利用接口来解耦。
java
package com.demo.observer.interfaces;
import com.demo.observer.concretes.Product;
/**
* 观察者接口 (IMember)
* 定义接收通知的统一方法
*/
public interface IMember {
void update(Product product); // 接收商品状态变化的通知
}
java
package com.demo.observer.interfaces;
/**
* 主题接口 (IProduct)
* 定义管理观察者(添加、移除、通知)的方法
*/
public interface IProduct {
void attach(IMember member); // 添加关注者
void detach(IMember member); // 移除关注者
void notifyObservers(); // 通知所有关注者
}
2.2 具体层:业务逻辑实现
具体主题 (ConcreteSubject):商品类,持有观察者列表。
java
package com.demo.observer.concretes;
import com.demo.observer.interfaces.IMember;
import com.demo.observer.interfaces.IProduct;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
@Getter
public class Product implements IProduct {
// 核心:维护一个观察者列表
private List<IMember> members = new ArrayList<>();
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// 当价格发生变化时,这是触发点
public void setPrice(double price) {
System.out.println("\n[系统消息]:商品 '" + this.name + "' 价格正在从 " + this.price + " 更新为 " + price);
this.price = price;
// 关键一步:状态改变后,立即通知观察者
this.notifyObservers();
}
@Override
public void attach(IMember member) {
if (!members.contains(member)) {
members.add(member);
}
}
@Override
public void detach(IMember member) {
members.remove(member);
}
@Override
public void notifyObservers() {
// 遍历通知所有关注该商品的会员
for (IMember member : members) {
member.update(this); // 将当前商品对象传给观察者
}
}
}
具体观察者 (ConcreteObserver):会员类,实现具体的响应逻辑。
java
package com.demo.observer.observers;
import com.demo.observer.interfaces.IMember;
import com.demo.observer.concretes.Product;
public class Member implements IMember {
private String name;
public Member(String name) {
this.name = name;
}
@Override
public void update(Product product) {
// 响应逻辑:收到通知后的具体操作
System.out.println(" [通知] -> 尊敬的会员 " + this.name + ":");
System.out.println(" 您关注的商品 '" + product.getName() + "' 价格已更新为 " + product.getPrice() + "元!");
}
}
2.3 客户端:模拟运行
java
package com.demo.observer;
import com.demo.observer.concretes.Product;
import com.demo.observer.interfaces.IMember;
import com.demo.observer.observers.Member;
public class OnlineStore {
public static void main(String[] args) {
// 1. 创建被观察者(商品)
Product phone = new Product("华为Mate 60 Pro", 6999.00);
// 2. 创建观察者(会员)
IMember memberZhang = new Member("张三");
IMember memberLi = new Member("李四");
IMember memberWang = new Member("王五");
// 3. 建立订阅关系
phone.attach(memberZhang);
phone.attach(memberLi);
System.out.println("--- 张三和李四关注了手机 ---");
// 4. 触发事件:商品降价
phone.setPrice(6499.00);
// 5. 动态调整订阅关系
System.out.println("\n--- 王五关注了手机,李四取消了关注 ---");
phone.attach(memberWang);
phone.detach(memberLi);
// 6. 再次触发事件:商品涨价
phone.setPrice(6699.00);
}
}
运行结果:
text
--- 张三和李四关注了手机 ---
[系统消息]:商品 '华为Mate 60 Pro' 价格正在从 6999.0 更新为 6499.0
[通知] -> 尊敬的会员 张三:
您关注的商品 '华为Mate 60 Pro' 价格已更新为 6499.0元!
[通知] -> 尊敬的会员 李四:
您关注的商品 '华为Mate 60 Pro' 价格已更新为 6499.0元!
--- 王五关注了手机,李四取消了关注 ---
[系统消息]:商品 '华为Mate 60 Pro' 价格正在从 6499.0 更新为 6699.0
[通知] -> 尊敬的会员 张三:
您关注的商品 '华为Mate 60 Pro' 价格已更新为 6699.0元!
[通知] -> 尊敬的会员 王五:
您关注的商品 '华为Mate 60 Pro' 价格已更新为 6699.0元!
3. 模式结构解析
UML 类图
实现
实现
聚合 (持有列表)
<<interface>>
IProduct
+attach(IMember)
+detach(IMember)
+notifyObservers()
Product
-List<IMember> members
-String name
-double price
+setPrice(double)
+attach(IMember)
+detach(IMember)
+notifyObservers()
<<interface>>
IMember
+update(Product)
Member
-String name
+update(Product)
核心工作流程
- 注册 (Attach) :
Product内部维护一个List<IMember>。 - 状态变更 :当
setPrice()被调用,商品价格改变。 - 触发通知 :
setPrice()内部调用notifyObservers()。 - 广播 :
notifyObservers()遍历列表,依次调用每个会员的update()方法。
4. JDK 中的观察者模式
在 Java 的早期版本(JDK 1.0)中,官方提供了 java.util.Observable 类和 java.util.Observer 接口来实现此模式。
4.1 简单的对比
| 特性 | 我们实现的模式 (Interface方案) | JDK 原生模式 (Class继承方案) |
|---|---|---|
| 被观察者类型 | 接口 IProduct |
类 Observable |
| 观察者类型 | 接口 IMember |
接口 Observer |
| 通知机制 | 列表遍历 + 自定义方法 | setChanged() + notifyObservers() |
| 灵活性 | 高 (实现接口,不占继承位) | 低 (必须继承 Observable 类) |
4.2 JDK 9 废弃了 Observable
如果你查看 JDK 9+ 的文档,会发现 Observable 被标记为 @Deprecated。原因主要有三点:
- 继承的局限性 :
Observable是一个类 而不是接口。Java 是单继承的,如果你的业务类(如Product)已经继承了别的父类(比如BaseEntity),就无法再继承Observable,这限制了复用性。 - 线程安全问题 :
Observable内部使用Vector来存储观察者,虽然Vector是线程安全的,但在高并发场景下,锁的粒度较大,且关于"如果在通知过程中有观察者被移除会发生什么"的保护机制不够灵活。 - 无法序列化 :
Observable没有实现Serializable接口。
4.3 现在的推荐做法
如果不想自己手写观察者模式,可以使用以下替代方案:
- Java Beans :
java.beans.PropertyChangeSupport(适合属性监听)。 - Java 9 Flow API :
java.util.concurrent.Flow(响应式编程,支持背压)。 - 第三方库 :Guava 的
EventBus或 Spring 的ApplicationEvent(发布-订阅模型)。
5. 总结
优点
- 解耦合 :
Product不需要知道Member的具体实现,只知道它实现了IMember接口。 - 开闭原则 :如果以后增加了新的观察者类型(比如"管理员通知"),只需实现
IMember接口,无需修改Product的代码。 - 动态性:可以在运行时随意添加或删除观察者。
缺点
- 循环依赖:如果观察者和被观察者互相引用,可能导致死循环。
- 性能问题 :如果观察者非常多,遍历通知会比较耗时(可以通过异步通知解决)。
一句话总结:
观察者模式就是 "别给我打电话,有事我会通知你" 的典型体现,它是实现系统组件解耦的利器